Merge pull request 'test: add Playwright e2e smoke suite for punimtag dev' (#60) from test/e2e-playwright-smoke into dev
This commit is contained in:
commit
0f24e7e6ba
@ -67,3 +67,58 @@ jobs:
|
||||
fi
|
||||
docker run --rm -v "$PWD:/repo" ghcr.io/gitleaks/gitleaks:latest \
|
||||
detect --source /repo --no-banner --redact ${extra}
|
||||
|
||||
# Playwright smoke via @levkin/playkit — public host guards + API health.
|
||||
# Sign-out auth spec runs only when E2E_ADMIN_* secrets are present.
|
||||
e2e:
|
||||
needs: skip-ci-check
|
||||
if: needs.skip-ci-check.outputs.should-skip != '1'
|
||||
runs-on: [homelab, self-hosted, linux]
|
||||
defaults:
|
||||
run:
|
||||
working-directory: e2e
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20'
|
||||
|
||||
- name: Allow private playkit clone
|
||||
if: ${{ secrets.GITEA_TOKEN != '' }}
|
||||
run: |
|
||||
git config --global url."https://oauth2:${{ secrets.GITEA_TOKEN }}@git.levkin.ca/".insteadOf "https://git.levkin.ca/"
|
||||
|
||||
- name: Install e2e deps
|
||||
run: npm ci
|
||||
|
||||
- name: Install Chromium
|
||||
run: npx playwright install --with-deps chromium
|
||||
|
||||
- name: Run Playwright smoke
|
||||
env:
|
||||
PLAYKIT_BASE_URL: ${{ secrets.PLAYKIT_BASE_URL }}
|
||||
PLAYKIT_API_BASE_URL: ${{ secrets.PLAYKIT_API_BASE_URL }}
|
||||
PLAYKIT_PROJECT: punimtag
|
||||
PLAYKIT_ENV: dev
|
||||
E2E_ADMIN_EMAIL: ${{ secrets.E2E_ADMIN_EMAIL }}
|
||||
E2E_ADMIN_PASSWORD: ${{ secrets.E2E_ADMIN_PASSWORD }}
|
||||
MAILTRAP_API_TOKEN: ${{ secrets.MAILTRAP_API_TOKEN }}
|
||||
MAILTRAP_ACCOUNT_ID: ${{ secrets.MAILTRAP_ACCOUNT_ID }}
|
||||
MAILTRAP_INBOX_ID: ${{ secrets.MAILTRAP_INBOX_ID }}
|
||||
CI: 'true'
|
||||
run: |
|
||||
export PLAYKIT_BASE_URL="${PLAYKIT_BASE_URL:-https://punimtagdev.levkin.ca}"
|
||||
export PLAYKIT_API_BASE_URL="${PLAYKIT_API_BASE_URL:-http://10.0.10.121:8000}"
|
||||
npx playwright test
|
||||
|
||||
- name: Upload report
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: playwright-report
|
||||
path: |
|
||||
e2e/playwright-report/
|
||||
e2e/test-results/
|
||||
retention-days: 7
|
||||
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -85,3 +85,11 @@ data/thumbnails/
|
||||
# PM2 ecosystem config (server-specific paths)
|
||||
ecosystem.config.js
|
||||
data/web_videos/
|
||||
|
||||
# Playwright e2e
|
||||
e2e/node_modules/
|
||||
e2e/playwright-report/
|
||||
e2e/test-results/
|
||||
e2e/blob-report/
|
||||
e2e/.auth/
|
||||
e2e/.env
|
||||
|
||||
17
e2e/.env.example
Normal file
17
e2e/.env.example
Normal file
@ -0,0 +1,17 @@
|
||||
# Public UI (required for host-guard tests)
|
||||
PLAYKIT_BASE_URL=https://punimtagdev.levkin.ca
|
||||
PLAYKIT_PROJECT=punimtag
|
||||
PLAYKIT_ENV=dev
|
||||
|
||||
# FastAPI on the DEV LXC (runners on LAN can reach this)
|
||||
PLAYKIT_API_BASE_URL=http://10.0.10.121:8000
|
||||
|
||||
# Dedicated e2e user — never commit real passwords
|
||||
E2E_ADMIN_EMAIL=
|
||||
E2E_ADMIN_PASSWORD=
|
||||
|
||||
# Mailtrap Email Testing (optional — forgot-password specs skip if unset)
|
||||
# MAILTRAP_API_TOKEN=
|
||||
# MAILTRAP_ACCOUNT_ID=
|
||||
# MAILTRAP_INBOX_ID=
|
||||
# E2E_MAIL_TO= # defaults to E2E_ADMIN_EMAIL
|
||||
40
e2e/README.md
Normal file
40
e2e/README.md
Normal file
@ -0,0 +1,40 @@
|
||||
# PunimTag e2e (Playwright + @levkin/playkit)
|
||||
|
||||
Smoke tests against the **public** DEV URL so we catch LAN-redirect bugs
|
||||
(e.g. `NEXTAUTH_URL=http://10.0.10.121:3001` → Kolby #57).
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
cd e2e
|
||||
cp .env.example .env # edit credentials
|
||||
npm ci
|
||||
npx playwright install chromium
|
||||
npm test
|
||||
```
|
||||
|
||||
Private Gitea installs need auth once: set a local git `insteadOf` with `GITEA_TOKEN`
|
||||
before `npm ci`, then unset it. CI does this in `.gitea/workflows/ci.yml`.
|
||||
Never commit lockfile URLs that embed tokens.
|
||||
|
||||
## Environment
|
||||
|
||||
| Variable | Required | Purpose |
|
||||
|----------|----------|---------|
|
||||
| `PLAYKIT_BASE_URL` | yes (default in config) | `https://punimtagdev.levkin.ca` |
|
||||
| `PLAYKIT_API_BASE_URL` | no | Default `http://10.0.10.121:8000` (LAN API) |
|
||||
| `E2E_ADMIN_EMAIL` | for sign-out spec | Dedicated e2e user (not a human password) |
|
||||
| `E2E_ADMIN_PASSWORD` | for sign-out spec | Dedicated e2e password |
|
||||
|
||||
Without credentials, the public-host login-page + API health specs still run;
|
||||
sign-out is skipped.
|
||||
|
||||
**Secrets:** store in Infisical `LevkinOps` → sync to Gitea Actions. See ansible
|
||||
`docs/hardening/SECRETS.md`.
|
||||
|
||||
## What we assert
|
||||
|
||||
1. Login page loads on the public hostname
|
||||
2. Sign-out stays on that hostname (never `10.x`)
|
||||
3. FastAPI `/health` → `{ "status": "ok" }`
|
||||
4. Forgot-password email appears in Mailtrap; reset link uses public host
|
||||
55
e2e/fixtures.ts
Normal file
55
e2e/fixtures.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { test as base, expect } from '@playwright/test';
|
||||
import {
|
||||
createPlaykitRuntime,
|
||||
MailtrapClient,
|
||||
type PlaykitFixtures,
|
||||
} from '@levkin/playkit';
|
||||
import { LoginPage } from './pages/LoginPage';
|
||||
import { AccountMenu } from './pages/AccountMenu';
|
||||
|
||||
const runtime = createPlaykitRuntime({
|
||||
...process.env,
|
||||
PLAYKIT_PROJECT: process.env.PLAYKIT_PROJECT || 'punimtag',
|
||||
PLAYKIT_ENV: process.env.PLAYKIT_ENV || 'dev',
|
||||
PLAYKIT_API_BASE_URL:
|
||||
process.env.PLAYKIT_API_BASE_URL ||
|
||||
process.env.API_BASE_URL ||
|
||||
'http://10.0.10.121:8000',
|
||||
PLAYKIT_BASE_URL:
|
||||
process.env.PLAYKIT_BASE_URL ||
|
||||
process.env.BASE_URL ||
|
||||
'https://punimtagdev.levkin.ca',
|
||||
});
|
||||
|
||||
type PunimtagFixtures = PlaykitFixtures & {
|
||||
loginPage: LoginPage;
|
||||
accountMenu: AccountMenu;
|
||||
e2eCredentials: { email: string; password: string } | null;
|
||||
mailtrap: MailtrapClient | null;
|
||||
};
|
||||
|
||||
export const test = base.extend<PunimtagFixtures>({
|
||||
playkitConfig: async ({}, use) => use(runtime.playkitConfig),
|
||||
playkitLog: async ({}, use) => use(runtime.playkitLog),
|
||||
api: async ({}, use) => use(runtime.api),
|
||||
timings: async ({}, use) => use(runtime.timings),
|
||||
|
||||
loginPage: async ({ page, playkitConfig }, use) =>
|
||||
use(new LoginPage(page, playkitConfig.baseUrl)),
|
||||
accountMenu: async ({ page, playkitConfig }, use) =>
|
||||
use(new AccountMenu(page, playkitConfig.baseUrl)),
|
||||
|
||||
e2eCredentials: async ({}, use) => {
|
||||
const email = process.env.E2E_ADMIN_EMAIL || process.env.E2E_EMAIL || '';
|
||||
const password = process.env.E2E_ADMIN_PASSWORD || process.env.E2E_PASSWORD || '';
|
||||
await use(email && password ? { email, password } : null);
|
||||
},
|
||||
|
||||
mailtrap: async ({ playkitLog }, use) => {
|
||||
await use(
|
||||
MailtrapClient.fromEnv(process.env, playkitLog.child({ component: 'mailtrap' })),
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
export { expect };
|
||||
117
e2e/package-lock.json
generated
Normal file
117
e2e/package-lock.json
generated
Normal file
@ -0,0 +1,117 @@
|
||||
{
|
||||
"name": "punimtag-e2e",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "punimtag-e2e",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@levkin/playkit": "git+https://git.levkin.ca/ilia/playkit.git#v0.2.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/node": "^22.15.0",
|
||||
"typescript": "^5.8.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@levkin/playkit": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "git+https://git.levkin.ca/ilia/playkit.git#2f3991dadc172aae86b98538eb5654cdb3237e67",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@playwright/test": ">=1.40.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@playwright/test": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.61.1",
|
||||
"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",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"dev": 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",
|
||||
"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",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"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",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
}
|
||||
}
|
||||
}
|
||||
21
e2e/package.json
Normal file
21
e2e/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "punimtag-e2e",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "PunimTag Playwright e2e — powered by @levkin/playkit",
|
||||
"scripts": {
|
||||
"test": "playwright test",
|
||||
"test:ui": "playwright test --ui",
|
||||
"test:headed": "playwright test --headed",
|
||||
"report": "playwright show-report"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@levkin/playkit": "git+https://git.levkin.ca/ilia/playkit.git#v0.2.0",
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@types/node": "^22.15.0",
|
||||
"typescript": "^5.8.0"
|
||||
}
|
||||
}
|
||||
18
e2e/pages/AccountMenu.ts
Normal file
18
e2e/pages/AccountMenu.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { type Page } from '@playwright/test';
|
||||
import { BasePage, waitForVisible } from '@levkin/playkit';
|
||||
|
||||
export class AccountMenu extends BasePage {
|
||||
constructor(page: Page, baseUrl: string) {
|
||||
super(page, baseUrl);
|
||||
}
|
||||
|
||||
async openMenu(): Promise<void> {
|
||||
await this.click(this.page.getByLabel('Account menu'));
|
||||
await waitForVisible(this.page.getByRole('button', { name: 'Sign out' }));
|
||||
}
|
||||
|
||||
async signOut(): Promise<void> {
|
||||
await this.openMenu();
|
||||
await this.click(this.page.getByRole('button', { name: 'Sign out' }));
|
||||
}
|
||||
}
|
||||
18
e2e/pages/LoginPage.ts
Normal file
18
e2e/pages/LoginPage.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { type Page } from '@playwright/test';
|
||||
import { BasePage } from '@levkin/playkit';
|
||||
|
||||
export class LoginPage extends BasePage {
|
||||
constructor(page: Page, baseUrl: string) {
|
||||
super(page, baseUrl);
|
||||
}
|
||||
|
||||
async openLogin(): Promise<void> {
|
||||
await this.open('/login');
|
||||
}
|
||||
|
||||
async signIn(email: string, password: string): Promise<void> {
|
||||
await this.fill(this.page.locator('#email'), email);
|
||||
await this.fill(this.page.locator('#password'), password);
|
||||
await this.click(this.page.locator('button[type="submit"]'));
|
||||
}
|
||||
}
|
||||
27
e2e/playwright.config.ts
Normal file
27
e2e/playwright.config.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
const baseURL =
|
||||
process.env.PLAYKIT_BASE_URL ||
|
||||
process.env.BASE_URL ||
|
||||
'https://punimtagdev.levkin.ca';
|
||||
|
||||
export default defineConfig({
|
||||
testDir: './tests',
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 1 : 0,
|
||||
workers: process.env.CI ? 2 : undefined,
|
||||
reporter: process.env.CI
|
||||
? [['list'], ['html', { open: 'never' }], ['junit', { outputFile: 'test-results/junit.xml' }]]
|
||||
: [['list'], ['html', { open: 'on-failure' }]],
|
||||
timeout: 60_000,
|
||||
expect: { timeout: 15_000 },
|
||||
use: {
|
||||
baseURL,
|
||||
trace: 'retain-on-failure',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
...devices['Desktop Chrome'],
|
||||
},
|
||||
projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }],
|
||||
});
|
||||
10
e2e/tests/api.health.spec.ts
Normal file
10
e2e/tests/api.health.spec.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { test, expect } from '../fixtures';
|
||||
|
||||
test.describe('api @smoke', () => {
|
||||
test('backend /health returns ok', async ({ api, timings }) => {
|
||||
const res = await timings.measure('api_health', () =>
|
||||
api.get<{ status: string }>('/health', { expectedStatus: 200 }),
|
||||
);
|
||||
expect(res.data).toMatchObject({ status: 'ok' });
|
||||
});
|
||||
});
|
||||
58
e2e/tests/auth.signout.spec.ts
Normal file
58
e2e/tests/auth.signout.spec.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import {
|
||||
assertPublicHost,
|
||||
waitForUrlHost,
|
||||
} from '@levkin/playkit';
|
||||
import { test, expect } from '../fixtures';
|
||||
|
||||
test.describe('auth @smoke', () => {
|
||||
test('login page loads on public host', async ({
|
||||
page,
|
||||
playkitConfig,
|
||||
timings,
|
||||
loginPage,
|
||||
}) => {
|
||||
assertPublicHost(playkitConfig.baseUrl);
|
||||
|
||||
await timings.measure('login_page', () => loginPage.openLogin());
|
||||
|
||||
await expect(page.getByRole('heading', { name: /Sign in/i })).toBeVisible();
|
||||
await expect(page.locator('#email')).toBeVisible();
|
||||
await waitForUrlHost(page, playkitConfig.expectedHost);
|
||||
expect(new URL(page.url()).hostname).toBe(playkitConfig.expectedHost);
|
||||
});
|
||||
|
||||
test('sign-out stays on public host (Kolby #57)', async ({
|
||||
page,
|
||||
playkitConfig,
|
||||
timings,
|
||||
loginPage,
|
||||
accountMenu,
|
||||
e2eCredentials,
|
||||
}) => {
|
||||
test.skip(
|
||||
!e2eCredentials,
|
||||
'Set E2E_ADMIN_EMAIL and E2E_ADMIN_PASSWORD (Infisical / Gitea secrets)',
|
||||
);
|
||||
|
||||
assertPublicHost(playkitConfig.baseUrl);
|
||||
|
||||
await timings.measure('login', async () => {
|
||||
await loginPage.openLogin();
|
||||
await loginPage.signIn(e2eCredentials!.email, e2eCredentials!.password);
|
||||
await page.getByLabel('Account menu').waitFor({ state: 'visible' });
|
||||
});
|
||||
|
||||
await waitForUrlHost(page, playkitConfig.expectedHost);
|
||||
|
||||
await timings.measure('sign_out', () => accountMenu.signOut());
|
||||
|
||||
// After sign-out, NextAuth must redirect to the public host — never 10.x.
|
||||
await waitForUrlHost(page, playkitConfig.expectedHost);
|
||||
expect(new URL(page.url()).hostname).toBe(playkitConfig.expectedHost);
|
||||
expect(page.url()).not.toMatch(/10\.\d+\.\d+\.\d+/);
|
||||
|
||||
await expect(page.getByRole('button', { name: /Sign in/i })).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
});
|
||||
});
|
||||
105
e2e/tests/forgot-password.mail.spec.ts
Normal file
105
e2e/tests/forgot-password.mail.spec.ts
Normal file
@ -0,0 +1,105 @@
|
||||
import {
|
||||
assertPublicHost,
|
||||
firstLinkMatching,
|
||||
waitForUrlHost,
|
||||
} from '@levkin/playkit';
|
||||
import { test, expect } from '../fixtures';
|
||||
|
||||
/**
|
||||
* Kolby #56 class: forgot-password must actually produce a message we can read,
|
||||
* and the reset link must use the public host (not 10.x).
|
||||
*
|
||||
* Requires:
|
||||
* - MAILTRAP_API_TOKEN + MAILTRAP_INBOX_ID (+ optional MAILTRAP_ACCOUNT_ID)
|
||||
* - punimtag DEV SMTP → sandbox.smtp.mailtrap.io (see SECRETS.md)
|
||||
* - E2E_ADMIN_EMAIL (or E2E_MAIL_TO) for an existing verified user
|
||||
*/
|
||||
test.describe('mail @smoke', () => {
|
||||
test('forgot-password email lands in Mailtrap with public reset link', async ({
|
||||
request,
|
||||
playkitConfig,
|
||||
timings,
|
||||
mailtrap,
|
||||
e2eCredentials,
|
||||
}) => {
|
||||
test.skip(
|
||||
!mailtrap,
|
||||
'Set MAILTRAP_API_TOKEN and MAILTRAP_INBOX_ID (Infisical /playkit/punimtag)',
|
||||
);
|
||||
|
||||
const to =
|
||||
process.env.E2E_MAIL_TO ||
|
||||
e2eCredentials?.email ||
|
||||
process.env.E2E_ADMIN_EMAIL ||
|
||||
'';
|
||||
test.skip(!to, 'Set E2E_MAIL_TO or E2E_ADMIN_EMAIL');
|
||||
|
||||
assertPublicHost(playkitConfig.baseUrl);
|
||||
const after = new Date(Date.now() - 5_000);
|
||||
|
||||
const res = await timings.measure('forgot_password_api', () =>
|
||||
request.post(`${playkitConfig.baseUrl}/api/auth/forgot-password`, {
|
||||
data: { email: to },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}),
|
||||
);
|
||||
expect(res.ok()).toBeTruthy();
|
||||
|
||||
const msg = await timings.measure('mailtrap_wait', () =>
|
||||
mailtrap!.waitForEmail({
|
||||
to,
|
||||
subject: /reset|password/i,
|
||||
after,
|
||||
timeoutMs: 90_000,
|
||||
pollMs: 2_500,
|
||||
}),
|
||||
);
|
||||
|
||||
const html = await mailtrap!.getHtml(msg.id, msg.html_path);
|
||||
const link = firstLinkMatching(html, /reset-password/);
|
||||
expect(link, 'reset link missing from email HTML').toBeTruthy();
|
||||
|
||||
const url = new URL(link!);
|
||||
expect(url.hostname).toBe(playkitConfig.expectedHost);
|
||||
expect(url.hostname).not.toMatch(/^10\./);
|
||||
expect(url.pathname).toMatch(/reset-password/);
|
||||
});
|
||||
|
||||
test('forgot-password UI shows success and Mailtrap gets mail', async ({
|
||||
page,
|
||||
playkitConfig,
|
||||
timings,
|
||||
mailtrap,
|
||||
e2eCredentials,
|
||||
}) => {
|
||||
test.skip(!mailtrap, 'Set MAILTRAP_API_TOKEN and MAILTRAP_INBOX_ID');
|
||||
const to =
|
||||
process.env.E2E_MAIL_TO ||
|
||||
e2eCredentials?.email ||
|
||||
process.env.E2E_ADMIN_EMAIL ||
|
||||
'';
|
||||
test.skip(!to, 'Set E2E_MAIL_TO or E2E_ADMIN_EMAIL');
|
||||
|
||||
assertPublicHost(playkitConfig.baseUrl);
|
||||
const after = new Date(Date.now() - 5_000);
|
||||
|
||||
await timings.measure('open_home', () => page.goto(playkitConfig.baseUrl));
|
||||
await page.getByRole('button', { name: /Sign in/i }).click();
|
||||
await page.getByRole('button', { name: /Forgot password/i }).click();
|
||||
await page.locator('#forgot-email').fill(to);
|
||||
await page.getByRole('button', { name: /Send reset link/i }).click();
|
||||
|
||||
await expect(page.getByRole('status')).toContainText(/sent|check your inbox/i, {
|
||||
timeout: 20_000,
|
||||
});
|
||||
await waitForUrlHost(page, playkitConfig.expectedHost);
|
||||
|
||||
const msg = await mailtrap!.waitForEmail({
|
||||
to,
|
||||
subject: /reset|password/i,
|
||||
after,
|
||||
timeoutMs: 90_000,
|
||||
});
|
||||
expect(msg.id).toBeTruthy();
|
||||
});
|
||||
});
|
||||
12
e2e/tsconfig.json
Normal file
12
e2e/tsconfig.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["./**/*.ts"]
|
||||
}
|
||||
@ -19,7 +19,9 @@
|
||||
"lint:python": "flake8 backend --max-line-length=100 --ignore=E501,W503,W293,E305,F401,F811,W291,W391,E712,W504,F841,E402,F824,E128,E226,F402,F541,E302,E117,E722 || true",
|
||||
"lint:python:syntax": "find backend -name '*.py' -exec python -m py_compile {} \\;",
|
||||
"test:backend": "export PYTHONPATH=$(pwd) && export SKIP_DEEPFACE_IN_TESTS=1 && ./venv/bin/python3 -m pytest tests/ -v",
|
||||
"test:e2e": "npm test --prefix e2e",
|
||||
"test:all": "npm run test:backend",
|
||||
"install:e2e": "npm install --prefix e2e",
|
||||
"ci:local": "npm run lint:all && npm run type-check:viewer && npm run lint:python && npm run test:backend && npm run build:all",
|
||||
"deploy:dev": "npm run build:all && echo '✅ Build complete. Ready for deployment to dev server (10.0.10.121)'",
|
||||
"deploy:dev:prepare": "npm run build:all && mkdir -p deploy/package && cp -r backend deploy/package/ && cp -r admin-frontend/dist deploy/package/admin-frontend-dist && cp -r viewer-frontend/.next deploy/package/viewer-frontend-next && cp requirements.txt deploy/package/ && cp .env.example deploy/package/ && echo '✅ Deployment package prepared in deploy/package/'"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user