mirror of
https://github.com/karatektus/pl_mail.git
synced 2026-10-25 01:54:10 +00:00
testing
This commit is contained in:
1 parent
cd3c78c205
commit
517bd5d345
13 files changed
+588
-32
No files matched your search
@@ -1,3 +1,26 @@
|
||||
# define your env variables for the test env here
|
||||
KERNEL_CLASS='App\Kernel'
|
||||
APP_SECRET='$ecretf0rt3st'
|
||||
# .env.test — committed defaults for the `test` environment (E2E + PHPUnit).
|
||||
# Real environment variables and .env.test.local override anything here.
|
||||
# DO NOT put real secrets in this file; these values are throwaway CI defaults.
|
||||
|
||||
APP_ENV=test
|
||||
APP_DEBUG=1
|
||||
|
||||
# Fixed so CSRF tokens are stable within a run. Test-only value.
|
||||
APP_SECRET=282065e0f71bf96c0dbcf0496c1f5d3e
|
||||
|
||||
# Throwaway libsodium key for the encrypted_string Doctrine type. Test-only.
|
||||
# Regenerate with: php -r 'echo base64_encode(sodium_crypto_secretbox_keygen());'
|
||||
APP_ENCRYPTION_KEY=emL6R8zs0pSXCVV2HvVvbFubriWJIla7kgnqSGqCJ8E=
|
||||
|
||||
# No real mail is sent, and async work is collected but not processed so the
|
||||
# suite never needs a running worker.
|
||||
MAILER_DSN=null://null
|
||||
MESSENGER_TRANSPORT_DSN=in-memory://
|
||||
|
||||
# Single source of truth for the seeded test user. The seed command, the
|
||||
# login template prefill, and the Playwright specs all read these.
|
||||
APP_DEV_USER_EMAIL=e2e@plmail.test
|
||||
APP_DEV_USER_PASSWORD=e2e-password-change-me
|
||||
|
||||
# Local default DB. CI overrides DATABASE_URL to point at its Postgres service.
|
||||
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=18&charset=utf8"
|
||||
@@ -0,0 +1,100 @@
|
||||
name: E2E
|
||||
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
playwright:
|
||||
name: Playwright (login + account)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
APP_ENV: test
|
||||
APP_DEBUG: "1"
|
||||
DATABASE_URL: "postgresql://app:app@127.0.0.1:5432/app?serverVersion=18&charset=utf8"
|
||||
MESSENGER_TRANSPORT_DSN: "in-memory://"
|
||||
MAILER_DSN: "null://null"
|
||||
APP_DEV_USER_EMAIL: "e2e@plmail.test"
|
||||
APP_DEV_USER_PASSWORD: "e2e-password-change-me"
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:18
|
||||
env:
|
||||
POSTGRES_USER: app
|
||||
POSTGRES_PASSWORD: app
|
||||
POSTGRES_DB: app
|
||||
ports:
|
||||
- 5432:5432
|
||||
options: >-
|
||||
--health-cmd "pg_isready -U app"
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: "8.4"
|
||||
extensions: ctype, iconv, pcntl, pdo_pgsql, pgsql, intl, sodium, mbstring, xml, curl, zip
|
||||
coverage: none
|
||||
tools: composer:v2
|
||||
|
||||
- name: Get Composer cache directory
|
||||
id: composer-cache
|
||||
run: echo "dir=$(composer config cache-files-dir)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Cache Composer dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: composer-${{ hashFiles('**/composer.lock') }}
|
||||
restore-keys: composer-
|
||||
|
||||
- name: Install PHP dependencies
|
||||
run: composer install --no-interaction --prefer-dist --no-progress
|
||||
|
||||
- name: Install Symfony CLI
|
||||
run: |
|
||||
curl -1sLf 'https://get.symfony.com/cli/setup.deb.sh' | sudo -E bash
|
||||
sudo apt-get install -y symfony-cli
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
|
||||
- name: Install Node dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Build frontend assets
|
||||
run: |
|
||||
php bin/console importmap:install
|
||||
php bin/console tailwind:build
|
||||
|
||||
- name: Prepare database
|
||||
run: |
|
||||
php bin/console doctrine:database:create --if-not-exists --no-interaction
|
||||
php bin/console doctrine:migrations:migrate --no-interaction --allow-no-migration
|
||||
|
||||
- name: Seed E2E user
|
||||
run: php bin/console app:e2e:seed-user
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: npx playwright test
|
||||
|
||||
- name: Upload Playwright report
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 14
|
||||
Generated
+109
@@ -0,0 +1,109 @@
|
||||
{
|
||||
"name": "plmail-e2e",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "plmail-e2e",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@types/node": "^22.10.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.61.1.tgz",
|
||||
"integrity": "sha512-8nKv6+0RJSL9FE4jYOEGXnPeM/Hg12qZpmqzZjRh3qM0Y7c3z1mrOTfFLids72RDQYVh9WpLEfR5WdpNX4fkig==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "22.20.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.1.tgz",
|
||||
"integrity": "sha512-EANqOCF9QFyra+4pfxUcX9STKJpCLjMbObVzljIJomAWSnuSIEAvyzEU53GaajbXJEgdh0iEcPL+DGvpUd4k1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.61.1.tgz",
|
||||
"integrity": "sha512-DWnY5o3YbLWK4GovuAVwpqL+1VwGNdUGrRr++8j8PtQQzvAVZUIMjKQ90fY689sEJZJBbZVw1rXaOKSTitkzPQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.61.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.61.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.61.1.tgz",
|
||||
"integrity": "sha512-h7Qlt6m4REp25qvIdvbDtVmD4LqVXfpRxhORv9L0jzETM05p4fuPJ3dKyuSXQxDSbXnmS79HAgi9589lGSpLkg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "6.21.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "plmail-e2e",
|
||||
"private": true,
|
||||
"description": "End-to-end tests for plMail (Playwright). Separate from the app's AssetMapper/importmap frontend.",
|
||||
"scripts": {
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"test:e2e:report": "playwright show-report",
|
||||
"typecheck": "tsc --noEmit -p tsconfig.json"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.55.0",
|
||||
"@types/node": "^22.10.0",
|
||||
"typescript": "^5.6.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* plMail end-to-end configuration.
|
||||
*
|
||||
* The app is booted via the Symfony local server in the `test` environment.
|
||||
* A dedicated `setup` project logs in through the real UI once and saves the
|
||||
* authenticated storage state, which the main `chromium` project reuses so
|
||||
* every non-auth spec starts already signed in.
|
||||
*/
|
||||
|
||||
const BASE_URL = process.env.E2E_BASE_URL ?? "http://127.0.0.1:8000";
|
||||
const STORAGE_STATE = "playwright/.auth/user.json";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./tests/e2e",
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: process.env.CI
|
||||
? [["github"], ["html", { open: "never" }]]
|
||||
: [["list"], ["html", { open: "never" }]],
|
||||
|
||||
use: {
|
||||
baseURL: BASE_URL,
|
||||
trace: "on-first-retry",
|
||||
screenshot: "only-on-failure",
|
||||
video: "retain-on-failure",
|
||||
},
|
||||
|
||||
projects: [
|
||||
{
|
||||
name: "setup",
|
||||
testMatch: /.*\.setup\.ts/,
|
||||
},
|
||||
{
|
||||
name: "chromium",
|
||||
use: {
|
||||
...devices["Desktop Chrome"],
|
||||
storageState: STORAGE_STATE,
|
||||
},
|
||||
dependencies: ["setup"],
|
||||
},
|
||||
],
|
||||
|
||||
webServer: {
|
||||
command: "symfony serve --port=8000 --no-tls --allow-http",
|
||||
url: `${BASE_URL}/login`,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 120_000,
|
||||
env: {
|
||||
APP_ENV: "test",
|
||||
APP_DEBUG: "1",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Command\Test;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
/**
|
||||
* Seeds a single, known user for end-to-end tests.
|
||||
*
|
||||
* Idempotent: find-or-create by email, and the password is (re)hashed on
|
||||
* every run so the credentials always match what the E2E suite expects.
|
||||
*
|
||||
* Credentials default to the APP_DEV_USER_EMAIL / APP_DEV_USER_PASSWORD
|
||||
* environment variables so the console command, the login template prefill,
|
||||
* and the Playwright specs all read from a single source of truth.
|
||||
*
|
||||
* Refuses to run in prod — this is a test-fixture tool.
|
||||
*/
|
||||
#[AsCommand(
|
||||
name: 'app:test:seed-user',
|
||||
description: 'Create or update the known user used by the end-to-end test suite',
|
||||
)]
|
||||
final class SeedTestUserCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private readonly EntityManagerInterface $entityManager,
|
||||
private readonly UserRepository $userRepository,
|
||||
private readonly UserPasswordHasherInterface $passwordHasher,
|
||||
#[Autowire('%kernel.environment%')]
|
||||
private readonly string $environment,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->addOption('email', null, InputOption::VALUE_REQUIRED, 'Override the seeded email')
|
||||
->addOption('password', null, InputOption::VALUE_REQUIRED, 'Override the seeded password');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
if ('prod' === $this->environment) {
|
||||
$io->error('app:e2e:seed-user must not run in the prod environment.');
|
||||
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$email = $input->getOption('email')
|
||||
?? $_SERVER['APP_DEV_USER_EMAIL']
|
||||
?? 'e2e@plmail.test';
|
||||
|
||||
$plainPassword = $input->getOption('password')
|
||||
?? $_SERVER['APP_DEV_USER_PASSWORD']
|
||||
?? 'e2e-password-change-me';
|
||||
|
||||
$user = $this->userRepository->findOneBy(['email' => $email]);
|
||||
$created = false;
|
||||
|
||||
if (null === $user) {
|
||||
$user = new User();
|
||||
$user
|
||||
->setEmail($email)
|
||||
->setNameFirst('E2E')
|
||||
->setNameLast('Tester')
|
||||
->setRoles([User::ROLE_USER]);
|
||||
|
||||
$created = true;
|
||||
}
|
||||
|
||||
$user->setPassword(
|
||||
$this->passwordHasher->hashPassword($user, $plainPassword)
|
||||
);
|
||||
|
||||
$this->entityManager->persist($user);
|
||||
$this->entityManager->flush();
|
||||
|
||||
if (true === $created) {
|
||||
$io->success(sprintf('Created E2E user "%s".', $email));
|
||||
} else {
|
||||
$io->success(sprintf('Updated E2E user "%s".', $email));
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Tests\Controller;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
|
||||
final class MailControllerTest extends WebTestCase
|
||||
{
|
||||
public function testIndex(): void
|
||||
{
|
||||
$client = static::createClient();
|
||||
$client->request('GET', '/mail');
|
||||
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Runs authenticated via the shared storage state from auth.setup.ts.
|
||||
*
|
||||
* Adding a password (IMAP) account only persists the row — no IMAP
|
||||
* connection is attempted at creation time — so a fake host is fine and the
|
||||
* test needs no mail server.
|
||||
*/
|
||||
test.describe("mail account creation", () => {
|
||||
test("adds an IMAP account from the settings modal", async ({ page }) => {
|
||||
// Unique per run so re-runs against a non-fresh DB stay unambiguous.
|
||||
const stamp = Date.now();
|
||||
const label = `E2E IMAP ${stamp}`;
|
||||
const username = `imap-${stamp}@example.test`;
|
||||
const host = `imap-${stamp}.example.test`;
|
||||
|
||||
await page.goto("/settings?section=accounts");
|
||||
|
||||
const accountsSection = page
|
||||
.locator("section")
|
||||
.filter({ has: page.locator("#settings-account-list") });
|
||||
|
||||
await accountsSection
|
||||
.getByRole("button", { name: "Add account" })
|
||||
.click();
|
||||
|
||||
// Wait for the Turbo-Frame modal form to load.
|
||||
const imapHost = page.locator('input[name="account[imapHost]"]');
|
||||
await expect(imapHost).toBeVisible();
|
||||
|
||||
// The IMAP/SMTP tab is active by default, so these fields are visible.
|
||||
await page.locator('input[name="account[email]"]').fill(label);
|
||||
await page.locator('input[name="account[username]"]').fill(username);
|
||||
await page.locator('input[name="account[password]"]').fill("hunter2");
|
||||
await imapHost.fill(host);
|
||||
await page.locator('input[name="account[imapPort]"]').fill("993");
|
||||
await page
|
||||
.locator('select[name="account[imapEncryption]"]')
|
||||
.selectOption("ssl");
|
||||
|
||||
await page.locator('#modal button[type="submit"]').click();
|
||||
|
||||
// Turbo Stream: success toast + refreshed settings list.
|
||||
await expect(page.getByText("Account added successfully")).toBeVisible();
|
||||
|
||||
const list = page.locator("#settings-account-list");
|
||||
await expect(list).toContainText(label);
|
||||
await expect(list).toContainText(host);
|
||||
});
|
||||
|
||||
test("keeps the modal open and reports the error when required fields are missing", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/settings?section=accounts");
|
||||
|
||||
const accountsSection = page
|
||||
.locator("section")
|
||||
.filter({ has: page.locator("#settings-account-list") });
|
||||
|
||||
await accountsSection
|
||||
.getByRole("button", { name: "Add account" })
|
||||
.click();
|
||||
|
||||
const imapHost = page.locator('input[name="account[imapHost]"]');
|
||||
await expect(imapHost).toBeVisible();
|
||||
|
||||
// Submit with the required fields blank.
|
||||
await page.locator('#modal button[type="submit"]').click();
|
||||
|
||||
// Required fields (native HTML5 + server-side NotBlank) keep us on the
|
||||
// form inside the modal rather than emitting the success toast.
|
||||
await expect(page.locator('input[name="account[imapHost]"]')).toBeVisible();
|
||||
await expect(page.getByText("Account added successfully")).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { test as setup } from "@playwright/test";
|
||||
import { login } from "./support/config";
|
||||
|
||||
const STORAGE_STATE = "playwright/.auth/user.json";
|
||||
|
||||
/**
|
||||
* Signs in through the real UI and saves the authenticated cookies so the
|
||||
* main project can reuse the session without re-logging in per test.
|
||||
*/
|
||||
setup("authenticate", async ({ page }) => {
|
||||
await login(page);
|
||||
await page.context().storageState({ path: STORAGE_STATE });
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { TEST_USER, login } from "./support/config";
|
||||
|
||||
/**
|
||||
* Login is exercised in a fresh, unauthenticated context — separate from the
|
||||
* shared storage state produced by auth.setup.ts.
|
||||
*/
|
||||
test.use({ storageState: { cookies: [], origins: [] } });
|
||||
|
||||
test.describe("login", () => {
|
||||
test("signs in with valid credentials and lands on the inbox", async ({
|
||||
page,
|
||||
}) => {
|
||||
await login(page);
|
||||
|
||||
// The login form is gone once we're in the authenticated shell.
|
||||
await expect(page.locator("#inputEmail")).toHaveCount(0);
|
||||
});
|
||||
|
||||
test("rejects invalid credentials and stays on the login page", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/login");
|
||||
|
||||
await page.locator("#inputEmail").fill(TEST_USER.email);
|
||||
await page.locator("#password").fill("definitely-the-wrong-password");
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
// The authenticator redirects back to /login on failure.
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
await expect(page.locator("#password")).toBeVisible();
|
||||
});
|
||||
|
||||
test("redirects anonymous users away from a protected page", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/settings?section=accounts");
|
||||
|
||||
await expect(page).toHaveURL(/\/login/);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Symfony\Component\Dotenv\Dotenv;
|
||||
|
||||
require dirname(__DIR__).'/vendor/autoload.php';
|
||||
|
||||
if (method_exists(Dotenv::class, 'bootEnv')) {
|
||||
(new Dotenv())->bootEnv(dirname(__DIR__).'/.env');
|
||||
}
|
||||
|
||||
if ($_SERVER['APP_DEBUG']) {
|
||||
umask(0000);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { expect, type Page } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Single source of truth for the seeded test user. Mirrors the
|
||||
* APP_DEV_USER_EMAIL / APP_DEV_USER_PASSWORD env vars consumed by the
|
||||
* `app:e2e:seed-user` console command.
|
||||
*/
|
||||
export const TEST_USER = {
|
||||
email: process.env.APP_DEV_USER_EMAIL ?? "e2e@plmail.test",
|
||||
password: process.env.APP_DEV_USER_PASSWORD ?? "e2e-password-change-me",
|
||||
};
|
||||
|
||||
/**
|
||||
* Drives the real login form at /login and waits for the authenticated
|
||||
* shell to land on the inbox.
|
||||
*/
|
||||
export async function login(
|
||||
page: Page,
|
||||
email: string = TEST_USER.email,
|
||||
password: string = TEST_USER.password,
|
||||
): Promise<void> {
|
||||
await page.goto("/login");
|
||||
|
||||
// The template may prefill from APP_DEV_USER_*; fill explicitly so the
|
||||
// test exercises real input regardless.
|
||||
await page.locator("#inputEmail").fill(email);
|
||||
await page.locator("#password").fill(password);
|
||||
|
||||
await page.getByRole("button", { name: "Sign in" }).click();
|
||||
|
||||
await expect(page).toHaveURL(/\/mail\/inbox/);
|
||||
await expect(
|
||||
page.getByRole("button", { name: `User menu for ${email}` }),
|
||||
).toBeVisible();
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true
|
||||
},
|
||||
"include": ["tests/e2e/**/*.ts", "playwright.config.ts"]
|
||||
}
|
||||
Reference in new issue
Block a user