mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Add cross-browser e2e tests, field-limit cheat sheet, and docs
Verification: - Add Playwright harness (e2e/) covering Chromium, Firefox, and WebKit plus Android/iOS mobile emulations. Smoke tests assert: default flyer renders with no JS/console errors, @media print hides app chrome and keeps the flyer, anonymous npub generation (WebCrypto), legacy /nostr/ redirect preserves query+hash, and event-lookup URLs show the loading state instead of the default flyer. All six projects pass. - package.json/playwright.config.js wire up the projects and a managed static server; .gitignore excludes node_modules and Playwright output. Docs: - docs/flyer-field-limits.md: per-script character cheat sheet for each flyer field (single-line caps + multi-line guidance), measured by rendering each script at print size. - README: add a Features section matching the shipping client and a cross-browser verification section; link the cheat sheet. - AGENTS.md: document the e2e suite and the one-time WebKit Linux dep. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
562b7d1429
commit
c5b2c20fc9
9 files changed
+406
-5
No files matched your search
@@ -9,6 +9,11 @@ venv/
|
||||
# Node/browser tooling
|
||||
node_modules/
|
||||
|
||||
# Playwright e2e output
|
||||
test-results/
|
||||
playwright-report/
|
||||
playwright/.cache/
|
||||
|
||||
# Build and local output
|
||||
build/
|
||||
dist/
|
||||
|
||||
@@ -37,3 +37,12 @@ pytest -q
|
||||
```
|
||||
|
||||
For visual or print changes, also run a local static server and inspect the site root (`/`) in a browser.
|
||||
|
||||
Cross-browser end-to-end tests live in `e2e/` (Playwright; Chromium, Firefox, WebKit, plus mobile emulations). Run them after changes that touch rendering, print, key generation, URL/redirect handling, or the loading state:
|
||||
|
||||
```bash
|
||||
npm install && npx playwright install
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
WebKit/Safari on Linux needs `sudo npx playwright install-deps` once. See `e2e/README.md`. Test tooling is a dev dependency only — it must not pull runtime frontend dependencies from CDNs or otherwise weaken the static, host-agnostic client.
|
||||
@@ -1,11 +1,24 @@
|
||||
# VoxVera
|
||||
|
||||
VoxVera is now a static Nostr-powered flyer client.
|
||||
VoxVera is a static, Nostr-powered flyer client.
|
||||
|
||||
The active app lives at the site root ([`site/`](site/)). It lets a user author flyer content, publish it as a public Nostr event, and render the flyer later from an event ID or Nostr address. The frontend is intentionally self-contained: relays store the content, and any static host can serve the client.
|
||||
The active app lives at the site root ([`site/`](site/)). It lets anyone author flyer content, publish it as a public Nostr event, and re-render or reprint the flyer later from a Nostr address or event ID — no application server. The frontend is intentionally self-contained: Nostr relays store the content, and any static host can serve the client.
|
||||
|
||||
The previous Tor/OnionShare CLI version is preserved on the `legacy-tor` branch.
|
||||
|
||||
## Features
|
||||
|
||||
- **Author and publish.** Fill in the flyer fields and publish them as a Nostr `30078` parameterized replaceable event. Republishing with the same flyer name replaces the previous version.
|
||||
- **Anonymous by default.** The client generates an anonymous browser-side key to sign events. A NIP-07 browser extension is detected and used if present, but is never required.
|
||||
- **Open from a link or ID.** Load a flyer from an `naddr`, `nevent1`, `note1`, raw 64-char event ID, or `nostr:` URL. The lookup field tolerates copy/paste whitespace and newlines, so an event ID copied off a printed tear-off (where it wraps across two lines) still resolves.
|
||||
- **Short, scannable poster URLs.** Each poster URL is a standard NIP-19 `naddr` carrying a single relay hint, placed in the URL `#fragment` (no `?addr=` query). Shorter URLs make the tear-off QR code easier to scan. Older relay-list `naddr`s and `?addr=` URLs still resolve.
|
||||
- **Tear-off tabs.** The printable flyer has ten tear-off tabs. Each tab is a single hyperlinked call to action (the URL is the link, not printed as text) and prints the full event ID so anyone can re-find the flyer by typing it.
|
||||
- **Loading state on shared links.** Opening a poster/event link shows a localized "Loading content…" placeholder (and a "Could not load this flyer." state on failure) instead of the default flyer; the default sample content appears only on the bare site URL.
|
||||
- **14 languages with RTL support.** Flyer content and UI are localized; right-to-left scripts render correctly. Letter-spacing is applied per script so cursive (Arabic/Persian) and combining (Devanagari) scripts are not broken — see [the spec](docs/nostr-static-client-spec.md#letter-spacing-by-script).
|
||||
- **Fits-the-page editing.** Fields that would overflow the printable sheet are rejected as you type, with an inline error under the affected field. See the [field length cheat sheet](docs/flyer-field-limits.md) for how much text fits each field per language.
|
||||
- **QR codes.** The tear-off QR points at the poster URL (re-open and reprint); the main flyer QR points at the creator's content URL.
|
||||
- **Self-contained.** No build step and no CDNs — Nostr and QR libraries are vendored under `site/vendor/`.
|
||||
|
||||
## What Is In `main`
|
||||
|
||||
- `site/index.html`, `site/nostr-client.js`, `site/nostr-client.css`, `site/locales.js`: static editor/viewer client
|
||||
@@ -14,6 +27,7 @@ The previous Tor/OnionShare CLI version is preserved on the `legacy-tor` branch.
|
||||
- `site/CNAME`: custom-domain target
|
||||
- `voxvera/nostr/`: small Python schema/validation helpers
|
||||
- `docs/nostr-static-client-spec.md`: design notes and development plan
|
||||
- `docs/flyer-field-limits.md`: per-language field length cheat sheet
|
||||
|
||||
## Local Development
|
||||
|
||||
@@ -45,11 +59,23 @@ The client is served from the domain root (`site/index.html`). `site/nostr/index
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
node --check site/nostr-client.js
|
||||
pytest -q
|
||||
node --check site/nostr-client.js # client syntax
|
||||
pytest -q # Nostr source schema
|
||||
```
|
||||
|
||||
The Python tests only cover the Nostr source schema. The frontend is static and should be checked in-browser for layout and print behavior after UI changes.
|
||||
### Cross-browser end-to-end tests
|
||||
|
||||
[Playwright](https://playwright.dev) smoke tests run the client on Chromium, Firefox, and WebKit (Safari's engine), plus Android/iOS mobile emulations — covering rendering, the `@media print` path, WebCrypto key generation, the `/nostr/` redirect, and the loading state.
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npx playwright install # download the browser engines
|
||||
npm run test:e2e
|
||||
```
|
||||
|
||||
See [`e2e/README.md`](e2e/README.md) for per-engine coverage and the one-time Linux step for WebKit (`sudo npx playwright install-deps`).
|
||||
|
||||
The Python tests only cover the Nostr source schema. The frontend is static; the Playwright suite covers engine behavior, but real Safari/iOS print and clipboard behavior is still worth a manual pass before a release.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
# Flyer Field Length Cheat Sheet
|
||||
|
||||
How much text fits in each flyer field, by script.
|
||||
|
||||
VoxVera does **not** impose a fixed character count. The editor measures the
|
||||
rendered flyer live and rejects a keystroke the moment a field would overflow
|
||||
the printable sheet (you'll see an inline "too long" error under that field).
|
||||
So the real limit is enforced for you as you type — the numbers below are
|
||||
practical guidance for drafting copy, sizing translations, or prompting an LLM
|
||||
to write flyer text that will fit on the first try.
|
||||
|
||||
Capacity varies by **script**, not by individual language, for three reasons:
|
||||
|
||||
1. The flyer body font is monospace (`Courier New`), so within a script every
|
||||
character takes the same width regardless of upper/lowercase.
|
||||
2. Per-script letter-spacing policy (`ls-full` / `ls-cjk` / `ls-none`, see
|
||||
[`nostr-static-client-spec.md`](nostr-static-client-spec.md#letter-spacing-by-script)).
|
||||
3. Glyph width differs by script — CJK glyphs are full-width (≈2× a Latin
|
||||
character), Devanagari fallback glyphs are narrower, etc.
|
||||
|
||||
## Single-line fields (hard caps)
|
||||
|
||||
**Title**, **Subtitle**, and **Headline** are single-line: they never wrap, so
|
||||
these are the limits people actually run into. Counts are characters
|
||||
(codepoints).
|
||||
|
||||
| Languages | Title | Headline | Subtitle |
|
||||
|---|---|---|---|
|
||||
| Latin & Cyrillic & Hebrew — en, es, de, fr, pt, sw, tr, ru, he | ~28 | ~28 | ~32 |
|
||||
| Arabic & Persian — ar, fa | ~28 | ~28 | ~38 |
|
||||
| Devanagari — hi | ~36 | ~36 | ~51 |
|
||||
| CJK — ja, zh | ~17 | ~17 | ~23 |
|
||||
|
||||
## Multi-line fields (generous)
|
||||
|
||||
**Body** (content), **Link message** (`url_message`), and **Footer**
|
||||
(`footer_message`) wrap across multiple lines, so they hold far more and are
|
||||
bounded only by the remaining height of the sheet.
|
||||
|
||||
| Languages | Body (content) |
|
||||
|---|---|
|
||||
| Latin & Cyrillic & Hebrew | ~1,900 (a few hundred words) |
|
||||
| Arabic & Persian | ~1,850 |
|
||||
| Devanagari — hi | ~2,450 |
|
||||
| CJK — ja, zh | ~1,170 |
|
||||
|
||||
`url_message` and `footer_message` share the lower portion of the sheet and can
|
||||
hold roughly 1,200–4,800 characters depending on script, but for a readable
|
||||
flyer keep the link message to a sentence and the footer to a short line.
|
||||
|
||||
The **URL** field is a link, not body copy — keep it short both because it's
|
||||
shown verbatim on the flyer and because it becomes the main QR code (shorter
|
||||
URLs scan more reliably).
|
||||
|
||||
## Notes and caveats
|
||||
|
||||
- **Approximate.** These come from rendering each script in a headless browser
|
||||
at true print size (8.5in × 11in, content column 4.75in). The
|
||||
Latin/Cyrillic numbers (`Courier New`) are the most reliable; numbers for
|
||||
Hebrew, Arabic, Persian, Devanagari, and CJK depend on the system fallback
|
||||
font, which can differ slightly between devices and from print output.
|
||||
- **The app is authoritative.** If the editor accepts your text it fits; if it
|
||||
rejects it, shorten it. Treat this sheet as a target, not a contract.
|
||||
- **Redaction markup counts.** Wrapping text in `~~double tildes~~` renders it
|
||||
struck-through; the four `~` characters count toward the field length even
|
||||
though they don't appear on the flyer.
|
||||
- **Re-measuring.** If the flyer layout, font, or letter-spacing policy
|
||||
changes, these numbers should be re-derived by rendering each script at print
|
||||
size and binary-searching each field for the largest run of characters that
|
||||
does not overflow the field target or the sheet.
|
||||
@@ -0,0 +1,58 @@
|
||||
# Cross-browser end-to-end tests
|
||||
|
||||
[Playwright](https://playwright.dev) smoke tests that exercise the static client
|
||||
on every major browser engine and on mobile emulations, so a change that works
|
||||
in Chrome can be confirmed to work in Firefox and Safari (WebKit) too.
|
||||
|
||||
## Coverage
|
||||
|
||||
Projects are defined in [`../playwright.config.js`](../playwright.config.js):
|
||||
|
||||
| Project | Engine | Notes |
|
||||
|---|---|---|
|
||||
| `chromium-desktop` | Chromium | Chrome / Brave / Edge |
|
||||
| `firefox-desktop` | Gecko | Firefox |
|
||||
| `webkit-desktop` | WebKit | Safari on macOS |
|
||||
| `chrome-android` | Chromium | Pixel 5 emulation (touch, mobile UA) |
|
||||
| `safari-ios` | WebKit | iPhone 13 emulation — proxy for iOS Safari |
|
||||
| `firefox-mobileviewport` | Gecko | Mobile **viewport only** (Playwright Firefox has no touch/isMobile emulation) |
|
||||
|
||||
WebKit is the engine behind Safari and **every** iOS browser, so the WebKit
|
||||
projects are our stand-in for the Apple ecosystem without a Mac in the loop.
|
||||
They are not a perfect substitute for real Safari/iOS (notably print dialog
|
||||
behavior and clipboard gesture rules), so a real-device pass is still worth
|
||||
doing before a release.
|
||||
|
||||
The specs in [`flyer.spec.js`](flyer.spec.js) check the genuinely
|
||||
engine-sensitive surfaces:
|
||||
|
||||
- default flyer renders with **no uncaught JS / console errors**
|
||||
- `@media print` hides the app chrome and keeps the flyer (a print-media
|
||||
screenshot of the sheet is attached per engine for visual diffing)
|
||||
- anonymous key generation works (the **WebCrypto** path)
|
||||
- the legacy `/nostr/` redirect preserves the query string and `#hash`
|
||||
- an event-lookup URL shows the localized loading state, never the default flyer
|
||||
|
||||
## Running
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npx playwright install # download Chromium, Firefox, WebKit
|
||||
npm run test:e2e # all projects
|
||||
npx playwright test --project=firefox-desktop # one engine
|
||||
npm run report # open the HTML report (with print screenshots)
|
||||
```
|
||||
|
||||
Playwright starts and stops the static server itself (`python3 -m http.server`),
|
||||
so you do not need a server running first.
|
||||
|
||||
### Linux: WebKit system dependency
|
||||
|
||||
On a fresh Linux box WebKit needs extra system libraries. If the WebKit
|
||||
projects fail to launch, install them once:
|
||||
|
||||
```bash
|
||||
sudo npx playwright install-deps # or: sudo apt-get install -y libavif16
|
||||
```
|
||||
|
||||
The Chromium and Firefox projects run without this step.
|
||||
@@ -0,0 +1,89 @@
|
||||
// @ts-check
|
||||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
// These run once per project (engine/device) defined in playwright.config.js,
|
||||
// so every assertion below is validated on Chromium, Firefox, WebKit, and the
|
||||
// Android/iOS mobile emulations.
|
||||
|
||||
/** Collect uncaught errors + console.error so we can fail on JS that only one
|
||||
* engine throws (the classic "works in Chrome" trap). */
|
||||
function trackErrors(page) {
|
||||
const errors = [];
|
||||
page.on("pageerror", (e) => errors.push(`pageerror: ${e.message}`));
|
||||
page.on("console", (m) => {
|
||||
if (m.type() === "error") errors.push(`console.error: ${m.text()}`);
|
||||
});
|
||||
return errors;
|
||||
}
|
||||
|
||||
test.describe("VoxVera static client", () => {
|
||||
test("loads the default flyer with no JS errors", async ({ page }) => {
|
||||
const errors = trackErrors(page);
|
||||
await page.goto("/");
|
||||
const sheet = page.locator("#flyer-preview .container").first();
|
||||
await expect(sheet).toBeVisible();
|
||||
// Default sample content should render on the bare URL.
|
||||
await expect(page.locator(".content h1").first()).toContainText("TOP SECRET");
|
||||
// A real Letter sheet is taller than it is wide.
|
||||
const box = await sheet.boundingBox();
|
||||
expect(box && box.height).toBeGreaterThan(box ? box.width : 0);
|
||||
expect(errors, errors.join("\n")).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("print media hides app chrome and keeps the flyer", async ({ page }, testInfo) => {
|
||||
await page.goto("/");
|
||||
await page.emulateMedia({ media: "print" });
|
||||
// App header/toolbars must not print.
|
||||
await expect(page.locator("body > header")).toBeHidden();
|
||||
const sheet = page.locator(".container").first();
|
||||
await expect(sheet).toBeVisible();
|
||||
// Attach the printed-media render of the sheet for visual cross-engine diff.
|
||||
const shot = await sheet.screenshot();
|
||||
await testInfo.attach(`print-${testInfo.project.name}`, { body: shot, contentType: "image/png" });
|
||||
});
|
||||
|
||||
test("generates an anonymous npub (WebCrypto path)", async ({ page }) => {
|
||||
const errors = trackErrors(page);
|
||||
// The identity controls live in the editor panel (hidden in viewer mode).
|
||||
await page.goto("/#editor");
|
||||
await page.locator("#generate-anon-identity").click();
|
||||
await expect(page.locator("#author-npub-output")).toContainText(/^npub1[0-9a-z]+/);
|
||||
expect(errors, errors.join("\n")).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("legacy /nostr/ redirect preserves query + hash", async ({ page }) => {
|
||||
await page.goto("/nostr/?ref=qr#hello");
|
||||
await page.waitForURL((url) => url.pathname === "/" && url.hash === "#hello");
|
||||
expect(new URL(page.url()).pathname).toBe("/");
|
||||
expect(new URL(page.url()).search).toBe("?ref=qr");
|
||||
expect(new URL(page.url()).hash).toBe("#hello");
|
||||
});
|
||||
|
||||
test("event-lookup URL shows the loading state, not default content", async ({ page }) => {
|
||||
await page.goto("/");
|
||||
// Build a well-formed naddr in-page so we don't depend on the network.
|
||||
const naddr = await page.evaluate(() => {
|
||||
const t = window.NostrTools;
|
||||
return t.nip19.naddrEncode({
|
||||
identifier: "voxvera:crossbrowser-smoke",
|
||||
pubkey: "0".repeat(64),
|
||||
kind: 30078,
|
||||
relays: []
|
||||
});
|
||||
});
|
||||
expect(naddr).toMatch(/^naddr1/);
|
||||
// Navigating to the fragment should immediately swap in the localized
|
||||
// "Loading content..." placeholder rather than the default TOP SECRET flyer.
|
||||
// Go via about:blank so this is a full document load (a bare #hash change
|
||||
// on the same URL would not re-run the URL-event handler).
|
||||
await page.goto("about:blank");
|
||||
await page.goto(`/#${naddr}`);
|
||||
// The default flyer (with its h1 headings) must never appear; only the
|
||||
// status placeholder. Accept either loading or the eventual load-failed
|
||||
// message, since the offline fetch may resolve before we assert.
|
||||
await expect(page.locator(".flyer-status-message")).toContainText(
|
||||
/Loading content|Could not load this flyer/i
|
||||
);
|
||||
await expect(page.locator(".content h1")).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
Generated
+78
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "voxvera-e2e",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "voxvera-e2e",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.49.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
|
||||
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"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.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
|
||||
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.60.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.60.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
|
||||
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "voxvera-e2e",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "Cross-browser end-to-end tests for the VoxVera static Nostr flyer client.",
|
||||
"scripts": {
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"report": "playwright show-report"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.49.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
// @ts-check
|
||||
const { defineConfig, devices } = require("@playwright/test");
|
||||
|
||||
// Cross-browser + cross-device coverage for the static client. The same site
|
||||
// is exercised on each desktop engine (Chromium/Firefox/WebKit) and on mobile
|
||||
// emulations (Android Chrome, iOS Safari, and a mobile-viewport Firefox).
|
||||
//
|
||||
// WebKit is the engine behind Safari (macOS) and every iOS browser, so the
|
||||
// `webkit` and `Mobile Safari` projects are our proxy for the Apple ecosystem
|
||||
// without a Mac in the loop. Playwright manages the static server itself.
|
||||
|
||||
const PORT = 8799;
|
||||
|
||||
module.exports = defineConfig({
|
||||
testDir: "./e2e",
|
||||
timeout: 30000,
|
||||
expect: { timeout: 7000 },
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
reporter: [["list"], ["html", { open: "never" }]],
|
||||
use: {
|
||||
baseURL: `http://127.0.0.1:${PORT}`,
|
||||
trace: "on-first-retry",
|
||||
screenshot: "only-on-failure"
|
||||
},
|
||||
webServer: {
|
||||
command: `python3 -m http.server ${PORT} --directory site`,
|
||||
url: `http://127.0.0.1:${PORT}/`,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 30000
|
||||
},
|
||||
projects: [
|
||||
// ---- Desktop engines ----
|
||||
{ name: "chromium-desktop", use: { ...devices["Desktop Chrome"] } },
|
||||
{ name: "firefox-desktop", use: { ...devices["Desktop Firefox"] } },
|
||||
{ name: "webkit-desktop", use: { ...devices["Desktop Safari"] } },
|
||||
|
||||
// ---- Mobile emulations ----
|
||||
{ name: "chrome-android", use: { ...devices["Pixel 5"] } },
|
||||
{ name: "safari-ios", use: { ...devices["iPhone 13"] } },
|
||||
|
||||
// Playwright's Firefox engine does not support touch/isMobile device
|
||||
// descriptors, so this is a mobile-viewport layout check only (no touch).
|
||||
{
|
||||
name: "firefox-mobileviewport",
|
||||
use: {
|
||||
...devices["Desktop Firefox"],
|
||||
viewport: { width: 412, height: 915 }
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
Reference in new issue
Block a user