LUX_ENV=local|devnet|testnet|mainnet is now the only environment knob. e2e/env.ts
resolves every base URL, RPC endpoint, network ID and chain ID from NETWORKS.yaml
at runtime; playwright.config.ts and helpers.ts derive from it instead of keeping
their own copies. Those copies had already drifted — the config resolved the
testnet explorer to test-explore.lux.network, which is NXDOMAIN, while helpers.ts
used the real explore-test.lux.network. The retired two-axis ENV/LUX_NETWORK
model now fails loudly rather than being half-honoured.
Projects are viewports (mobile 390x844, tablet 834x1194, laptop 1440x900), so
"mobile and desktop" is the same three frames every run. Screenshots go to
~/Desktop/lux-e2e/<env>/<surface>-<viewport>.png on pass, and shot-reporter.ts
copies a failing test's dying state next to them — a failure you cannot look at
is a failure you cannot fix.
50-login.spec.ts proves sign-in for real: lux.cloud -> lux.id -> /dashboard, with
the password from LUX_E2E_PASSWORD only. It refuses the cheap answers, because
each one lies here — /dashboard is an SPA that returns 200 and keeps its URL when
anonymous, and /auth/callback renders "Sign-in failed" at 200. The proof is the
token: a JWT in sessionStorage whose iss is the IdP and whose email is the
account typed, which lux.id/v1/iam/oauth/userinfo accepts (401 bare, 401 for a
junk token). That is the leg that breaks under invalid_client. It also asserts
the handoff is a public PKCE client and carries a negative control so the
positive assertions cannot go vacuous. Verified with a wrong password: red.
Measured while building this, not assumed:
- workers are per-env. Mainnet is CDN-fronted and ran the gate at 4 with
342/342; testnet hits the raw LB and lost 66 cases to throttling at 4 that
all passed at 2.
- `locator('text=/block/i').first()` in two specs resolved to a hidden Radix
menu trigger, so both tracked the nav's collapse rather than the blocks
table. Replaced by one shared assertion on the section AND a block link.
- the token check runs inside the page: page.request.get attaches request
headers to a thrown error, which printed a live bearer token into the log.
- 40-lux-financial carried a plaintext password; it now comes from the
environment.
Co-authored-by: Hanzo Dev <[email protected]>
86 lines
3.4 KiB
TypeScript
86 lines
3.4 KiB
TypeScript
import { defineConfig } from '@playwright/test'
|
||
import { env, envBanner, VIEWPORTS, SHOT_DIR } from './e2e/env'
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// The multi-environment e2e suite.
|
||
// ---------------------------------------------------------------------------
|
||
// LUX_ENV=local | devnet | testnet | mainnet (default: mainnet)
|
||
//
|
||
// make e2e # mainnet, all three viewports
|
||
// make e2e-testnet # same specs, testnet endpoints
|
||
// make e2e-login # just the sign-in proof
|
||
// npx playwright test --project=mobile
|
||
//
|
||
// Every env-shaped value comes from e2e/env.ts, which reads NETWORKS.yaml — this
|
||
// config declares NO urls, ports or chain IDs of its own. It used to keep a
|
||
// third copy of those tables (and resolved the testnet explorer to the NXDOMAIN
|
||
// host test-explore.lux.network).
|
||
//
|
||
// PROJECTS ARE VIEWPORTS. "Mobile and desktop" therefore means the same three
|
||
// frames on every run: mobile 390x844, tablet 834x1194, laptop 1440x900. Run one
|
||
// with --project=<name>; screenshots carry the frame name so they never collide.
|
||
//
|
||
// The branded web3-surface matrix keeps its own config (playwright.branded.
|
||
// config.ts) with its own 4-breakpoint grid and matrix reporter — one way to run
|
||
// each suite, so this one testIgnores it.
|
||
export default defineConfig({
|
||
testDir: './e2e',
|
||
testIgnore: '**/branded/**',
|
||
// File-level parallelism only (fullyParallel:false keeps tests inside a file
|
||
// serial, which is where the ordering assumptions live). The worker count is
|
||
// per-environment and measured — see WORKERS in e2e/env.ts. On mainnet 1
|
||
// worker and 4 gave an IDENTICAL result set (154 passed / 2 failed) in 5.9m vs
|
||
// 2.3m; testnet throttles at 4 and is clean at 2. Everything but the login
|
||
// spec is read-only against live surfaces, and rpcCall() backs off on 429.
|
||
fullyParallel: false,
|
||
forbidOnly: !!process.env.CI,
|
||
// One retry locally, so a page that was slow to render under 4-worker load is
|
||
// reported as FLAKY rather than either failing the run or being silently
|
||
// green. Measured: with 4 workers, `09-defi-flow › explorer shows recent
|
||
// blocks` failed once at laptop and passed on its own in 13s. Flaky is a
|
||
// status, not a pass — a genuinely broken surface still fails twice and goes
|
||
// red. Never raise this to paper over a real failure.
|
||
retries: process.env.CI ? 2 : 1,
|
||
workers: env.workers,
|
||
timeout: 120_000,
|
||
expect: { timeout: 15_000 },
|
||
|
||
reporter: [
|
||
// Relocates every FAILURE's screenshot to SHOT_DIR so a red run is
|
||
// immediately viewable next to the deliberate snap() shots.
|
||
['./e2e/shot-reporter.ts'],
|
||
...(process.env.CI
|
||
? [['github'] as const, ['junit', { outputFile: 'e2e/results.xml' }] as const]
|
||
: [['list'] as const]),
|
||
['html', { open: 'never' }] as const,
|
||
],
|
||
|
||
use: {
|
||
baseURL: env.urls.exploreLux,
|
||
trace: 'on-first-retry',
|
||
screenshot: 'only-on-failure',
|
||
video: 'on-first-retry',
|
||
actionTimeout: 15_000,
|
||
navigationTimeout: 60_000,
|
||
ignoreHTTPSErrors: true,
|
||
},
|
||
|
||
projects: VIEWPORTS.map((v) => ({
|
||
name: v.name,
|
||
use: { viewport: { width: v.width, height: v.height } },
|
||
})),
|
||
|
||
outputDir: './e2e/test-results',
|
||
|
||
metadata: {
|
||
banner: envBanner(),
|
||
env: env.name,
|
||
network: env.network,
|
||
networkId: env.networkId,
|
||
chainIds: env.chainIds,
|
||
workers: env.workers,
|
||
shots: SHOT_DIR,
|
||
urls: env.urls,
|
||
},
|
||
})
|