Initial commit; adaptation from mascarpone

This commit is contained in:
Robert Perce 2026-05-30 12:15:04 -05:00
commit 3d45ec4b0a
36 changed files with 5877 additions and 0 deletions

22
e2e/README.md Normal file
View file

@ -0,0 +1,22 @@
# e2e
Install deps with `corepack pnpm i`.
Ensure that if you update `@playwright/test` that
(a) it remains a devdep, and
(b) it is a `=`-type dependency.
Start a dev server with `cargo run`. Tests expect an ephemeral user with username and
password both `test`. Achieve this with
```
cargo run set-password test
```
then
```
sqlite3 users.db "update users set ephemeral=true where username='test'"
```
Run tests in the docker image with the right browsers installed with `./Taskfile
playwright:local` or `./Taskfile playwright:ui`.

47
e2e/Taskfile Executable file
View file

@ -0,0 +1,47 @@
#!/usr/bin/env bash
PATH=$PATH:node_modules/.bin
SCRIPT_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd)
_playwright_version() {
jq -r '.devDependencies["@playwright/test"]' "$SCRIPT_DIR/package.json" | sed -e 's/\=/v/'
}
playwright:local() {
exec docker run \
--interactive --tty --rm --ipc=host --net=host \
--volume "$SCRIPT_DIR":/e2e:rw --env BASE_URL="$BASE_URL" \
"mcr.microsoft.com/playwright:$(_playwright_version)" \
bash -c "cd /e2e && env PROJECT_FILTER=firefox ./Taskfile _test $*"
}
playwright:ui() {
playwright:local --ui-host=0.0.0.0
}
playwright:ci() {
exec docker run \
--interactive --tty --rm --ipc=host --net=host \
--volume "$SCRIPT_DIR":/e2e:rw --env BASE_URL="$BASE_URL" \
"mcr.microsoft.com/playwright:$(_playwright_version)" \
bash -c "cd /e2e && ./Taskfile _test $*"
}
_test:full() {
if ! test -f /.dockerenv; then
echo "Don't run _test directly; use playwright:local."
fi
# run only in firefox first to see if there's errors and,
# only if not, run in everything else
env PROJECT_FILTER=firefox playwright test "$@" && \
env PROJECT_FILTER=!firefox playwright test "$@"
}
_test() {
if ! test -f /.dockerenv; then
echo "Don't run _test directly; use playwright:local."
fi
playwright test "$@"
}
"$@"

37
e2e/custom-expects.ts Normal file
View file

@ -0,0 +1,37 @@
import { expect, type Locator } from '@playwright/test';
expect.extend({
async toBeAbove(self: Locator, other: Locator) {
const name = 'toBeAbove';
let pass: boolean;
let matcherResult: any;
let selfY: number | null = null;
let otherY: number | null = null;
try {
selfY = (await self.boundingBox())?.y ?? null;
otherY = (await self.boundingBox())?.y ?? null;
pass = selfY !== null && otherY !== null && (selfY < otherY);
} catch (e: any) {
matcherResult = e.matcherResult;
pass = false;
}
if (this.isNot) {
pass =!pass;
}
const message = () => this.utils.matcherHint(name, undefined, undefined, { isNot: this.isNot }) +
'\n\n' +
`Locator: ${self}\n` +
`Expected: above ${other} (y=${this.utils.printExpected(otherY)})\n` +
(matcherResult ? `Received: y=${this.utils.printReceived(selfY)}` : '');
return {
message,
pass,
name,
expected: (this.isNot ? '>=' : '<') + otherY,
actual: selfY,
};
}
});

17
e2e/package.json Normal file
View file

@ -0,0 +1,17 @@
{
"name": "entretien/e2e",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo use Taskfile instead"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@playwright/test": "=1.57.0",
"@types/node": "^24.9.1"
},
"packageManager": "pnpm@10.28.1+sha512.7d7dbbca9e99447b7c3bf7a73286afaaf6be99251eb9498baefa7d406892f67b879adb3a1d7e687fc4ccc1a388c7175fbaae567a26ab44d1067b54fcb0d6a316"
}

11
e2e/pages/util.ts Normal file
View file

@ -0,0 +1,11 @@
import { expect } from '@playwright/test';
import type { Page } from '@playwright/test';
export const login = async (page: Page) => {
await page.goto('/login');
await page.getByLabel("Username").fill("test");
await page.getByLabel("Password").fill("test");
await page.getByRole("button", { name: /login/i }).click();
await page.waitForURL('/');
};

72
e2e/playwright.config.ts Normal file
View file

@ -0,0 +1,72 @@
import { defineConfig, devices } from '@playwright/test';
import './custom-expects';
// purposefully not using ??: we want to replace empty empty string with default
const BASE_URL = process.env.BASE_URL || 'http://localhost:3000';
let addlConfig = {
retries: process.env.CI ? 2 : 0,
};
let projects = [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
/* Test against mobile viewports. */
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
{
name: 'Mobile Safari',
use: { ...devices['iPhone 12'] },
},
];
const pfil = process.env.PROJECT_FILTER;
if (pfil) {
if (pfil.startsWith('!')) {
projects = projects.filter(p => p.name !== pfil.slice(1));
} else {
projects = projects.filter(p => p.name === pfil);
}
}
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './pages',
fullyParallel: true,
workers: 1,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: Boolean(process.env.CI),
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL: BASE_URL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects,
...addlConfig,
});

67
e2e/pnpm-lock.yaml generated Normal file
View file

@ -0,0 +1,67 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
devDependencies:
'@playwright/test':
specifier: '=1.57.0'
version: 1.57.0
'@types/node':
specifier: ^24.9.1
version: 24.10.1
packages:
'@playwright/test@1.57.0':
resolution: {integrity: sha512-6TyEnHgd6SArQO8UO2OMTxshln3QMWBtPGrOCgs3wVEmQmwyuNtB10IZMfmYDE0riwNR1cu4q+pPcxMVtaG3TA==}
engines: {node: '>=18'}
hasBin: true
'@types/node@24.10.1':
resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==}
fsevents@2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
playwright-core@1.57.0:
resolution: {integrity: sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==}
engines: {node: '>=18'}
hasBin: true
playwright@1.57.0:
resolution: {integrity: sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==}
engines: {node: '>=18'}
hasBin: true
undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
snapshots:
'@playwright/test@1.57.0':
dependencies:
playwright: 1.57.0
'@types/node@24.10.1':
dependencies:
undici-types: 7.16.0
fsevents@2.3.2:
optional: true
playwright-core@1.57.0: {}
playwright@1.57.0:
dependencies:
playwright-core: 1.57.0
optionalDependencies:
fsevents: 2.3.2
undici-types@7.16.0: {}

1
e2e/static Symbolic link
View file

@ -0,0 +1 @@
../static