CI / check (push) Successful in 29s
CI / tests-imaging-browsers (push) Successful in 1m37s
CI / tests-unit (push) Successful in 2m4s
CI / tests-integration (push) Successful in 2m12s
CI / tests-quality (push) Successful in 55s
CI / tests-ui (push) Successful in 4m31s
CI / preflight (push) Skipped
CI / deploy (push) Successful in 3m39s
The self-hosted CI runner packs several jobs onto the shared host network (capacity 4). The UI suite pinned the fixture server to 127.0.0.1:3107 and set reuseExistingServer: true, so overlapping runs fought over the bind: one job won it, and every browser in the other got "connection refused" on all four projects. Pass one random high port per run through the environment (shared by the main process and its worker) and only reuse an existing server outside CI. Verified with the full ui suite: 150 passed, 2 skipped.
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import { randomInt } from "node:crypto";
|
|
import { defineConfig } from "@playwright/test";
|
|
|
|
// The fixture server binds a loopback port. The CI runner packs several jobs
|
|
// onto the shared host network (capacity > 1), so a fixed port makes concurrent
|
|
// runs fight over the bind: one wins, the other's browser then gets
|
|
// "connection refused". Pick one random high port for the whole run and pass it
|
|
// to every worker through the environment, unless a port is pinned explicitly
|
|
// (e.g. a developer reusing a manually started fixture server).
|
|
function fixturePort() {
|
|
const requested = Number(process.env.UI_TEST_PORT);
|
|
if (Number.isInteger(requested) && requested >= 1024 && requested <= 65535)
|
|
return requested;
|
|
const shared = Number(process.env.PW_UI_TEST_PORT);
|
|
if (Number.isInteger(shared) && shared >= 1024 && shared <= 65535)
|
|
return shared;
|
|
const port = randomInt(30000, 50000);
|
|
process.env.PW_UI_TEST_PORT = String(port);
|
|
return port;
|
|
}
|
|
const port = fixturePort();
|
|
const baseURL = `http://127.0.0.1:${port}`;
|
|
export default defineConfig({
|
|
testDir: "./e2e/ui",
|
|
testMatch: "**/*.spec.ts",
|
|
fullyParallel: false,
|
|
workers: 1,
|
|
retries: process.env.CI ? 1 : 0,
|
|
timeout: 45000,
|
|
expect: {
|
|
timeout: 10000,
|
|
toHaveScreenshot: {
|
|
animations: "disabled",
|
|
caret: "hide",
|
|
maxDiffPixelRatio: 0.002,
|
|
},
|
|
},
|
|
snapshotPathTemplate: `{testDir}/__screenshots__/${process.platform}/{projectName}/{testFilePath}/{arg}{ext}`,
|
|
reporter: [
|
|
["list"],
|
|
["html", { outputFolder: "playwright-report/ui", open: "never" }],
|
|
],
|
|
outputDir: "test-results/ui",
|
|
use: {
|
|
baseURL,
|
|
locale: "en-US",
|
|
timezoneId: "UTC",
|
|
colorScheme: "dark",
|
|
contextOptions: { reducedMotion: "reduce" },
|
|
trace: "retain-on-failure",
|
|
screenshot: "only-on-failure",
|
|
launchOptions: process.env.UI_TEST_BROWSER_PATH
|
|
? { executablePath: process.env.UI_TEST_BROWSER_PATH }
|
|
: {},
|
|
},
|
|
projects: [
|
|
{
|
|
name: "desktop-chromium",
|
|
use: { browserName: "chromium", viewport: { width: 1280, height: 900 } },
|
|
},
|
|
{
|
|
name: "mobile-chromium",
|
|
use: {
|
|
browserName: "chromium",
|
|
viewport: { width: 390, height: 844 },
|
|
isMobile: true,
|
|
deviceScaleFactor: 1,
|
|
hasTouch: true,
|
|
},
|
|
},
|
|
{
|
|
name: "desktop-firefox",
|
|
use: { browserName: "firefox", viewport: { width: 1280, height: 900 } },
|
|
},
|
|
{
|
|
name: "desktop-webkit",
|
|
use: { browserName: "webkit", viewport: { width: 1280, height: 900 } },
|
|
},
|
|
],
|
|
webServer: {
|
|
command: "node e2e/ui/fixture-server.mjs",
|
|
url: `${baseURL}/health`,
|
|
timeout: 120000,
|
|
reuseExistingServer: !process.env.CI,
|
|
env: { UI_TEST_PORT: String(port) },
|
|
},
|
|
});
|