Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 80 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ jobs:
HOST_PROJECT_PATH: ${{ github.workspace }}
steps:
- uses: actions/checkout@v4
- name: Restore Composer cache
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
restore-keys: composer-
- name: Restore npm cache
uses: actions/cache@v4
with:
path: node_modules
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
Expand Down Expand Up @@ -78,6 +90,18 @@ jobs:
HOST_PROJECT_PATH: ${{ github.workspace }}
steps:
- uses: actions/checkout@v4
- name: Restore Composer cache
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
restore-keys: composer-
- name: Restore npm cache
uses: actions/cache@v4
with:
path: node_modules
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
Expand Down Expand Up @@ -105,6 +129,18 @@ jobs:
HOST_PROJECT_PATH: ${{ github.workspace }}
steps:
- uses: actions/checkout@v4
- name: Restore Composer cache
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
restore-keys: composer-
- name: Restore npm cache
uses: actions/cache@v4
with:
path: node_modules
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
Expand Down Expand Up @@ -162,6 +198,18 @@ jobs:
HOST_PROJECT_PATH: ${{ github.workspace }}
steps:
- uses: actions/checkout@v4
- name: Restore Composer cache
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
restore-keys: composer-
- name: Restore npm cache
uses: actions/cache@v4
with:
path: node_modules
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
Expand All @@ -172,6 +220,13 @@ jobs:
run: docker pull ${{ env.APP_IMAGE }}
- name: Start Docker services
run: docker compose -f docker-compose.yml -f docker-compose.ci.yml up -d app mariadb
- name: Wait for MariaDB
run: |
for i in {1..30}; do
docker compose exec -T mariadb mariadb -uroot -psecret -e "SELECT 1" && break
echo "Waiting for MariaDB... ($i/30)"
sleep 2
done
- name: Install dependencies
run: |
docker compose exec -T app mise trust
Expand Down Expand Up @@ -201,6 +256,18 @@ jobs:
COMPOSE_FILES: -f docker-compose.yml -f docker-compose.ci.yml -f docker-compose.e2e.yml
steps:
- uses: actions/checkout@v4
- name: Restore Composer cache
uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
restore-keys: composer-
- name: Restore npm cache
uses: actions/cache@v4
with:
path: node_modules
key: npm-${{ hashFiles('package-lock.json') }}
restore-keys: npm-
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
Expand All @@ -210,7 +277,7 @@ jobs:
- name: Pull app image
run: docker pull ${{ env.APP_IMAGE }}
- name: Start E2E stack
run: docker compose ${{ env.COMPOSE_FILES }} up -d app nginx mariadb
run: docker compose ${{ env.COMPOSE_FILES }} up -d app messenger nginx mariadb
- name: Wait for MariaDB
run: |
for i in $(seq 1 30); do
Expand Down Expand Up @@ -244,6 +311,18 @@ jobs:
docker compose ${{ env.COMPOSE_FILES }} exec -T app php bin/console doctrine:database:create --env=test
docker compose ${{ env.COMPOSE_FILES }} exec -T app php bin/console doctrine:migrations:migrate --no-interaction --env=test
docker compose ${{ env.COMPOSE_FILES }} exec -T app php bin/console app:e2e:create-user e2e@example.com --password=e2e-secret
- name: Restore Playwright deps cache
uses: actions/cache@v4
with:
path: tests/End2End/node_modules
key: playwright-deps-${{ hashFiles('tests/End2End/package-lock.json') }}
restore-keys: playwright-deps-
- name: Restore Playwright browsers cache
uses: actions/cache@v4
with:
path: /home/runner/.cache/ms-playwright
key: playwright-browsers-${{ hashFiles('tests/End2End/package-lock.json') }}
restore-keys: playwright-browsers-
- name: Install Playwright
run: |
cd tests/End2End
Expand Down
18 changes: 17 additions & 1 deletion .mise/tasks/tests/e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,14 @@ echo "End-to-end tests (Playwright)"
echo "BASE_URL=${BASE_URL}"
echo

restore_dev_stack() {
echo ""
echo "Restoring dev stack (docker compose up -d)..."
docker compose up -d
}

if [ "${NO_START}" != "true" ]; then
trap restore_dev_stack EXIT
echo "Starting stack with e2e override..."
docker compose $COMPOSE_FILES up -d
echo "Waiting for MariaDB..."
Expand Down Expand Up @@ -48,6 +55,15 @@ echo "Installing Playwright dependencies (if needed)..."
(cd tests/End2End && npx playwright install chromium 2>/dev/null || true)

echo "Running Playwright tests..."
set +e
(cd tests/End2End && BASE_URL="$BASE_URL" npx playwright test)
PLAYWRIGHT_EXIT=$?
set -e

if [ $PLAYWRIGHT_EXIT -eq 0 ]; then
echo "E2E tests completed!"
else
echo "E2E tests failed (exit code $PLAYWRIGHT_EXIT)."
fi

echo "E2E tests completed!"
exit $PLAYWRIGHT_EXIT
29 changes: 29 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ parameters:
photo_builder.openai_timeout_seconds: 120
llm_content_editor.simulate: "%env(bool:LLM_CONTENT_EDITOR_SIMULATE)%"

# E2E workspace fixture (used by SimulatedGitAdapter when APP_ENV=test)
e2e.workspace_fixture_path: "%kernel.project_dir%/tests/fixtures/e2e-workspace-template"

services:
# default configuration for services in *this* file
_defaults:
Expand Down Expand Up @@ -106,6 +109,9 @@ services:
App\RemoteContentAssets\Infrastructure\RemoteManifestFetcherInterface:
class: App\RemoteContentAssets\Infrastructure\RemoteManifestFetcher

App\RemoteContentAssets\Infrastructure\S3AssetUploaderInterface:
class: App\RemoteContentAssets\Infrastructure\S3AssetUploader

# Remote content assets facade - metadata for remote images (dimensions, size, mime type),
# manifest validation and fetching
App\RemoteContentAssets\Facade\RemoteContentAssetsFacadeInterface:
Expand All @@ -114,6 +120,7 @@ services:
- "@App\\RemoteContentAssets\\Infrastructure\\RemoteImageInfoFetcherInterface"
- "@App\\RemoteContentAssets\\Infrastructure\\RemoteManifestValidatorInterface"
- "@App\\RemoteContentAssets\\Infrastructure\\RemoteManifestFetcherInterface"
- "@App\\RemoteContentAssets\\Infrastructure\\S3AssetUploaderInterface"

# Sitebuilder-specific workspace tooling facade
App\WorkspaceTooling\Facade\WorkspaceToolingFacadeInterface:
Expand Down Expand Up @@ -210,3 +217,25 @@ services:

App\LlmContentEditor\Facade\LlmContentEditorFacadeInterface:
alias: App\LlmContentEditor\Facade\SwitchableLlmContentEditorFacade

when@test:
services:
# E2E/test doubles: no external HTTP or git/GitHub calls
App\RemoteContentAssets\Infrastructure\RemoteManifestValidatorInterface:
class: App\RemoteContentAssets\TestHarness\SimulatedRemoteManifestValidator

App\RemoteContentAssets\Infrastructure\S3AssetUploaderInterface:
class: App\RemoteContentAssets\TestHarness\SimulatedS3AssetUploader

App\WorkspaceMgmt\Infrastructure\Adapter\GitAdapterInterface:
class: App\WorkspaceMgmt\TestHarness\SimulatedGitAdapter
arguments:
$workspaceFixturePath: "%e2e.workspace_fixture_path%"
$realGitAdapter: "@App\\WorkspaceMgmt\\Infrastructure\\Adapter\\GitCliAdapter"

App\WorkspaceMgmt\Infrastructure\Adapter\GitHubAdapterInterface:
class: App\WorkspaceMgmt\TestHarness\SimulatedGitHubAdapter

# E2E: no-op setup steps (priority so it is used instead of DefaultProjectSetupStepsProvider)
App\WorkspaceMgmt\TestHarness\E2eNoOpProjectSetupStepsProvider:
tags: [{ name: workspace_mgmt.setup_steps_provider, priority: 100 }]
6 changes: 5 additions & 1 deletion docker-compose.e2e.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# E2E override: run app in test env, nginx with test env and fixed port for Playwright.
# E2E override: run app and messenger in test env, nginx with test env and fixed port for Playwright.
# Use: docker compose -f docker-compose.yml -f docker-compose.e2e.yml up -d
services:
app:
environment:
APP_ENV: test

messenger:
environment:
APP_ENV: test

nginx:
ports:
- "127.0.0.1:8080:80"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
{% endif %}

<div class="space-y-6"
data-test-id="editor-page"
{{ stimulus_controller('chat-based-content-editor', {
runUrl: runUrl,
pollUrlTemplate: pollUrlTemplate,
Expand Down Expand Up @@ -226,6 +227,7 @@
<label for="instruction" class="block text-sm font-medium text-dark-700 dark:text-dark-300 mb-1">{{ 'editor.input_label'|trans }}</label>
<textarea {{ stimulus_target('chat-based-content-editor', 'instruction') }}
{{ stimulus_action('chat-based-content-editor', 'handleKeydown', 'keydown') }}
data-test-id="editor-message-input"
name="instruction"
id="instruction"
rows="3"
Expand All @@ -236,6 +238,7 @@
<div class="flex items-center gap-4">
<button type="submit"
{{ stimulus_target('chat-based-content-editor', 'submit') }}
data-test-id="editor-send-button"
class="px-4 py-2 rounded-md bg-primary-600 text-white text-sm font-medium hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-dark-900 disabled:opacity-50 disabled:cursor-not-allowed">
{{ 'editor.make_changes'|trans }}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% block title %}{{ 'workspace.setup.title'|trans }}{% endblock %}

{% block content %}
<div class="max-w-2xl mx-auto space-y-6" {{ stimulus_controller('workspace-setup', {
<div class="max-w-2xl mx-auto space-y-6" data-test-id="workspace-setup-page" {{ stimulus_controller('workspace-setup', {
pollUrl: pollUrl,
redirectUrl: redirectUrl,
translations: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
{% endfor %}

<form method="POST"
data-test-id="project-form"
action="{{ project ? path('project_mgmt.presentation.update', { id: project.id }) : path('project_mgmt.presentation.create') }}"
class="space-y-6 bg-white dark:bg-dark-800 rounded-lg border border-dark-200 dark:border-dark-700 p-6"
{{ stimulus_controller('manifest-urls', {
Expand All @@ -31,6 +32,7 @@
<input type="text"
name="name"
id="name"
data-test-id="project-form-name"
value="{{ project ? project.name : '' }}"
required
placeholder="{{ 'project.form.placeholder_project_name'|trans }}"
Expand All @@ -42,6 +44,7 @@
<input type="url"
name="git_url"
id="git_url"
data-test-id="project-form-git-url"
value="{{ project ? project.gitUrl : '' }}"
required
placeholder="{{ 'project.form.placeholder_project_link'|trans }}"
Expand All @@ -57,6 +60,7 @@
<input type="password"
name="github_token"
id="github_token"
data-test-id="project-form-github-token"
value="{{ project ? (displayGithubToken is defined ? displayGithubToken : project.githubToken) : '' }}"
{{ (keysVisible is not defined or keysVisible) ? 'required' : 'disabled' }}
placeholder="{{ (keysVisible is defined and not keysVisible) ? 'project.form.keys_managed_by_deployment'|trans : 'project.form.placeholder_github_token'|trans }}"
Expand Down Expand Up @@ -93,6 +97,7 @@
<input type="password"
name="content_editing_api_key"
id="content_editing_api_key"
data-test-id="project-form-content-editing-api-key"
value="{{ project ? (displayContentEditingApiKey is defined ? displayContentEditingApiKey : project.contentEditingLlmModelProviderApiKey) : '' }}"
{{ (keysVisible is not defined or keysVisible) ? 'required' : 'disabled' }}
placeholder="{{ (keysVisible is defined and not keysVisible) ? 'project.form.keys_managed_by_deployment'|trans : 'project.form.placeholder_llm_api_key'|trans }}"
Expand Down Expand Up @@ -593,6 +598,7 @@
{{ 'common.cancel'|trans }}
</a>
<button type="submit"
data-test-id="project-form-submit"
class="px-4 py-2 rounded-md bg-primary-600 text-white text-sm font-medium hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-dark-900">
{{ project ? 'project.form.update_project_button'|trans : 'project.form.add_project_button'|trans }}
</button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
</div>
{% if projectsWithStatus is not empty %}
<a href="{{ path('project_mgmt.presentation.new') }}"
data-test-id="project-list-add-project"
class="inline-flex justify-center px-4 py-2 rounded-md bg-primary-600 text-white text-sm font-medium hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:focus:ring-offset-dark-900">
{{ 'project.list.add_project'|trans }}
</a>
Expand All @@ -26,6 +27,7 @@
<p class="mt-1 text-sm text-dark-500 dark:text-dark-400">{{ 'project.list.no_projects_description'|trans }}</p>
<div class="mt-6">
<a href="{{ path('project_mgmt.presentation.new') }}"
data-test-id="project-list-add-project"
class="inline-flex items-center px-4 py-2 rounded-md bg-primary-600 text-white text-sm font-medium hover:bg-primary-700">
{{ 'project.list.add_project'|trans }}
</a>
Expand All @@ -43,7 +45,7 @@
'MERGED': 'bg-indigo-100 text-indigo-800 dark:bg-indigo-900 dark:text-indigo-200',
'PROBLEM': 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
} %}
<div class="rounded-lg border border-dark-200 dark:border-dark-700 bg-white dark:bg-dark-900 p-4">
<div class="rounded-lg border border-dark-200 dark:border-dark-700 bg-white dark:bg-dark-900 p-4" data-test-class="project-list-item">
<div class="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
{# Project info section #}
<div class="flex-1 min-w-0 space-y-2">
Expand Down Expand Up @@ -103,6 +105,7 @@
</a>
{% else %}
<a href="{{ path('chat_based_content_editor.presentation.start', { projectId: item.project.id }) }}"
data-test-class="project-list-edit-content-link"
class="inline-flex items-center px-3 py-1.5 rounded-md bg-primary-600 text-white text-sm font-medium hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-primary-500">
{{ 'project.list.edit_content'|trans }}
</a>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace App\RemoteContentAssets\TestHarness;

use App\RemoteContentAssets\Infrastructure\RemoteManifestValidatorInterface;

/**
* E2E/test double: accepts any non-empty http(s) manifest URL without making HTTP requests.
*/
final class SimulatedRemoteManifestValidator implements RemoteManifestValidatorInterface
{
public function isValidManifestUrl(string $url): bool
{
$url = trim($url);
if ($url === '') {
return false;
}

$parsed = parse_url($url);
$scheme = $parsed['scheme'] ?? null;

return $scheme === 'http' || $scheme === 'https';
}
}
Loading