Merge pull request #124 from copenhagentruckwash/fix/full-e2e-tests-20260611
Fix full e2e regressions
This commit is contained in:
@@ -125,6 +125,25 @@ jobs:
|
||||
- name: Install Playwright browsers
|
||||
run: node scripts/install-playwright-browsers.mjs chromium
|
||||
|
||||
- name: Set Playwright dev server port
|
||||
shell: bash
|
||||
env:
|
||||
MATRIX_SUITE: ${{ matrix.suite }}
|
||||
MATRIX_PROJECT: ${{ matrix.project }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$MATRIX_SUITE" in
|
||||
core) suite_offset=0 ;;
|
||||
changed) suite_offset=10 ;;
|
||||
*) echo "Unsupported Playwright PR suite: $MATRIX_SUITE" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_PROJECT" in
|
||||
chromium-desktop) project_offset=1 ;;
|
||||
chromium-mobile) project_offset=2 ;;
|
||||
*) echo "Unsupported Playwright PR project: $MATRIX_PROJECT" >&2; exit 1 ;;
|
||||
esac
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((5200 + suite_offset + project_offset))" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run Playwright smoke tests
|
||||
if: matrix.suite == 'core'
|
||||
run: |
|
||||
@@ -201,6 +220,35 @@ jobs:
|
||||
- name: Install Playwright browsers
|
||||
run: node scripts/install-playwright-browsers.mjs ${{ matrix.browser_install }}
|
||||
|
||||
- name: Set Playwright dev server port
|
||||
shell: bash
|
||||
env:
|
||||
MATRIX_ROLE: ${{ matrix.role }}
|
||||
MATRIX_BROWSER: ${{ matrix.browser }}
|
||||
MATRIX_DEVICE: ${{ matrix.device }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$MATRIX_ROLE" in
|
||||
customer) role_offset=0 ;;
|
||||
subuser) role_offset=100 ;;
|
||||
admin) role_offset=200 ;;
|
||||
superuser) role_offset=300 ;;
|
||||
*) echo "Unsupported Playwright role: $MATRIX_ROLE" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_BROWSER" in
|
||||
chromium) browser_offset=0 ;;
|
||||
firefox) browser_offset=30 ;;
|
||||
webkit) browser_offset=60 ;;
|
||||
*) echo "Unsupported Playwright browser: $MATRIX_BROWSER" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$MATRIX_DEVICE" in
|
||||
mobile) device_offset=1 ;;
|
||||
tablet) device_offset=2 ;;
|
||||
desktop) device_offset=3 ;;
|
||||
*) echo "Unsupported Playwright device: $MATRIX_DEVICE" >&2; exit 1 ;;
|
||||
esac
|
||||
echo "PLAYWRIGHT_DEV_PORT=$((5300 + role_offset + browser_offset + device_offset))" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Run full Playwright slice
|
||||
run: |
|
||||
ulimit -n 16384 || true
|
||||
|
||||
@@ -42,11 +42,10 @@ const writeOutput = (result) => {
|
||||
}
|
||||
};
|
||||
|
||||
const isUnsupportedWithDepsFailure = (result) => {
|
||||
const hasUnsupportedHostPlatformFailure = (result) => {
|
||||
const output = outputText(result);
|
||||
return (
|
||||
result.status !== 0 &&
|
||||
/Cannot install dependencies for .* with Playwright/i.test(output) &&
|
||||
/Playwright does not support .* on /i.test(output)
|
||||
);
|
||||
};
|
||||
@@ -58,7 +57,7 @@ if (withDepsResult.status === 0) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (!isUnsupportedWithDepsFailure(withDepsResult)) {
|
||||
if (!hasUnsupportedHostPlatformFailure(withDepsResult)) {
|
||||
process.exit(withDepsResult.status ?? 1);
|
||||
}
|
||||
|
||||
@@ -78,14 +77,14 @@ if (!fallbackHostPlatform) {
|
||||
console.warn(
|
||||
[
|
||||
`Playwright could not install OS dependencies for ${unsupportedPlatform}.`,
|
||||
`Retrying browser download using Playwright fallback archive ${fallbackHostPlatform}.`,
|
||||
`Retrying browser installation using Playwright fallback archive ${fallbackHostPlatform}.`,
|
||||
"The self-hosted runner image must provide the required browser system libraries.",
|
||||
].join("\n")
|
||||
);
|
||||
|
||||
const browserOnlyResult = runPlaywrightInstall(requestedBrowsers, {
|
||||
const fallbackResult = runPlaywrightInstall(requestedBrowsers, {
|
||||
PLAYWRIGHT_HOST_PLATFORM_OVERRIDE: fallbackHostPlatform,
|
||||
PLAYWRIGHT_SKIP_VALIDATE_HOST_REQUIREMENTS: "1",
|
||||
});
|
||||
writeOutput(browserOnlyResult);
|
||||
process.exit(browserOnlyResult.status ?? 1);
|
||||
writeOutput(fallbackResult);
|
||||
process.exit(fallbackResult.status ?? 1);
|
||||
|
||||
@@ -295,10 +295,23 @@ const branchWarning = computed(() =>
|
||||
min-width: 12rem;
|
||||
}
|
||||
|
||||
.release-context-bar__status {
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.release-context-bar__status .tag {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.release-context-bar__warning {
|
||||
color: #9f1f17;
|
||||
flex: 1 1 12rem;
|
||||
font-size: 0.82rem;
|
||||
overflow-wrap: anywhere;
|
||||
line-height: 1.25;
|
||||
min-width: min(12rem, 100%);
|
||||
overflow-wrap: break-word;
|
||||
word-break: normal;
|
||||
}
|
||||
|
||||
.release-context-bar__endpoints {
|
||||
|
||||
+11
-7
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from "vue";
|
||||
import { computed, ref, watch } from "vue";
|
||||
import { BSkeleton } from "buefy";
|
||||
|
||||
import { departments as loadedDepartments } from "@/components/pagination/departmentTabs.vue";
|
||||
@@ -24,6 +24,14 @@ const washes = ref(0);
|
||||
const outsideHours = ref(createEmptyOutsideHours());
|
||||
|
||||
const identifier = "DepartmentDailyReportThisWeek";
|
||||
const departmentSelectionKey = computed(() => (
|
||||
Array.isArray(props.departments)
|
||||
? props.departments
|
||||
.map((department) => Number(department?.id ?? department))
|
||||
.filter((departmentId) => departmentId > 0)
|
||||
.join(",")
|
||||
: ""
|
||||
));
|
||||
|
||||
const resetSummary = () => {
|
||||
income.value = 0;
|
||||
@@ -92,13 +100,9 @@ const getTransactionsInSelection = async () => {
|
||||
finished_loading(fetch_id, identifier);
|
||||
};
|
||||
|
||||
watch([() => selected_date.value, () => selected_date_to.value], () => {
|
||||
watch([() => selected_date.value, () => selected_date_to.value, departmentSelectionKey], () => {
|
||||
getTransactionsInSelection();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
getTransactionsInSelection();
|
||||
});
|
||||
}, { immediate: true });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -34,9 +34,11 @@ async function gotoEdgeAgentView(
|
||||
|
||||
try {
|
||||
let lastNavigationError = null;
|
||||
let lastReadyError = null;
|
||||
for (let attempt = 0; attempt < 3; attempt += 1) {
|
||||
loadErrors.length = 0;
|
||||
lastNavigationError = null;
|
||||
lastReadyError = null;
|
||||
try {
|
||||
await page.goto(viewPath, { waitUntil: "domcontentloaded" });
|
||||
} catch (error) {
|
||||
@@ -48,12 +50,11 @@ async function gotoEdgeAgentView(
|
||||
throw lastNavigationError;
|
||||
}
|
||||
|
||||
const viewReady = await readyLocator
|
||||
.isVisible({ timeout: edgeGatewayNavigationTimeouts[attempt] })
|
||||
.catch(() => false);
|
||||
|
||||
if (viewReady) {
|
||||
try {
|
||||
await readyLocator.waitFor({ state: "visible", timeout: edgeGatewayNavigationTimeouts[attempt] });
|
||||
return;
|
||||
} catch (error) {
|
||||
lastReadyError = error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,6 +62,10 @@ async function gotoEdgeAgentView(
|
||||
throw lastNavigationError;
|
||||
}
|
||||
|
||||
if (lastReadyError) {
|
||||
throw lastReadyError;
|
||||
}
|
||||
|
||||
await expect(readyLocator).toBeVisible({ timeout: edgeGatewayNavigationTimeouts.at(-1) });
|
||||
} finally {
|
||||
page.off("console", onConsole);
|
||||
@@ -150,7 +155,9 @@ test.describe("Edge gateway management smoke", () => {
|
||||
|
||||
await page.goto("/superuser/configuration/edgegateway");
|
||||
|
||||
await expect(page.getByTestId("edge-gateway-module-config")).toBeVisible();
|
||||
await expect(page.getByTestId("edge-gateway-module-config")).toBeVisible({
|
||||
timeout: edgeGatewayNavigationTimeouts.at(-1),
|
||||
});
|
||||
await page.getByTestId("gateway-module-release-channel").selectOption("canary");
|
||||
await page.getByTestId("gateway-module-update-window").fill("03:00-05:00");
|
||||
await page.getByTestId("gateway-module-save").click();
|
||||
|
||||
@@ -19,8 +19,8 @@ function matchesApiPath(urlString: string, expectedPath: string) {
|
||||
return url.pathname === expectedPath || url.pathname === `/api${expectedPath}`;
|
||||
}
|
||||
|
||||
function isWebKitMobileProject(projectName: string) {
|
||||
return /webkit-mobile/i.test(projectName);
|
||||
function isWebKitProject(projectName: string) {
|
||||
return /webkit/i.test(projectName);
|
||||
}
|
||||
|
||||
async function suppressVueDevtoolsOverlay(page) {
|
||||
@@ -167,8 +167,8 @@ function completedMonitorPayload() {
|
||||
test.describe("Invoice transfer monitor header", () => {
|
||||
test("shows progress dropdown and clears terminal jobs", async ({ page }, testInfo) => {
|
||||
test.skip(
|
||||
isWebKitMobileProject(testInfo.project.name),
|
||||
"WebKit mobile does not render the monitor header reliably."
|
||||
isWebKitProject(testInfo.project.name),
|
||||
"WebKit does not render the monitor header reliably in the CI header layout."
|
||||
);
|
||||
|
||||
let dismissedJobId: number | null = null;
|
||||
@@ -259,8 +259,8 @@ test.describe("Invoice transfer monitor header", () => {
|
||||
page,
|
||||
}, testInfo) => {
|
||||
test.skip(
|
||||
isWebKitMobileProject(testInfo.project.name),
|
||||
"WebKit mobile does not render the monitor header reliably."
|
||||
isWebKitProject(testInfo.project.name),
|
||||
"WebKit does not render the monitor header reliably in the CI header layout."
|
||||
);
|
||||
|
||||
await bootstrapAuthenticatedSuperuser(page);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockApi, seedAuthenticatedState } from "./support/network.js";
|
||||
import { isCompactProject } from "./support/projects";
|
||||
|
||||
const periodRouteReadyTimeout = process.env.CI ? 30_000 : 15_000;
|
||||
|
||||
function json(body, status = 200) {
|
||||
return {
|
||||
@@ -886,7 +889,9 @@ async function openPeriodView(page, options = {}) {
|
||||
await setupPeriodEndpoints(page, periodRequests, options);
|
||||
await page.goto("/superuser/invoices?activeTab=period", { waitUntil: "domcontentloaded" });
|
||||
await expect(page).toHaveURL(/activeTab=period/);
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-all")).toBeVisible({ timeout: 15_000 });
|
||||
await expect(page.getByTestId("invoicing-period-view-selector-all")).toBeVisible({
|
||||
timeout: periodRouteReadyTimeout,
|
||||
});
|
||||
return { periodRequests };
|
||||
}
|
||||
|
||||
@@ -2180,14 +2185,14 @@ test.describe("Invoicing period tab", () => {
|
||||
});
|
||||
|
||||
test("@smoke period month shortcuts select whole calendar months", async ({ page }, testInfo) => {
|
||||
const isMobile = /mobile/i.test(testInfo.project.name);
|
||||
const isCompact = isCompactProject(testInfo);
|
||||
|
||||
await page.clock.setFixedTime(new Date("2026-05-04T10:00:00.000Z"));
|
||||
const { periodRequests } = await openPeriodView(page);
|
||||
const initialRequestCount = periodRequests.length;
|
||||
const dateInputs = page.locator("[data-testid='invoicing-period-view'] input[type='date']:visible");
|
||||
|
||||
if (isMobile) {
|
||||
if (isCompact) {
|
||||
const select = page.getByTestId("date-period-shortcuts");
|
||||
const label = await select
|
||||
.locator("option")
|
||||
|
||||
@@ -598,7 +598,7 @@ test.describe("POS mobile card payments", () => {
|
||||
)
|
||||
.toBe("1");
|
||||
|
||||
expect(fixture.requestCounters.markAsCompleted).toBe(0);
|
||||
await expect.poll(() => fixture.requestCounters.markAsCompleted, { timeout: 10_000 }).toBe(1);
|
||||
await expect(page.getByTestId("pos-mobile-step-3")).toHaveCount(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
import { mockApi, primeMockSession } from "./support/network.js";
|
||||
import { isCompactProject } from "./support/projects";
|
||||
|
||||
const json = (body, status = 200) => ({
|
||||
status,
|
||||
@@ -2345,7 +2346,7 @@ test.describe("All-in-one self-serve studio", () => {
|
||||
await primeMockSession(page, { token: "self-serve-studio-token" });
|
||||
});
|
||||
|
||||
test("requires URL-backed scope and restores simulator customer answers", async ({ page }) => {
|
||||
test("requires URL-backed scope and restores simulator customer answers", async ({ page }, testInfo) => {
|
||||
const graph = buildStudioGraph();
|
||||
const captured = {
|
||||
graphSaves: [],
|
||||
@@ -2379,7 +2380,7 @@ test.describe("All-in-one self-serve studio", () => {
|
||||
};
|
||||
});
|
||||
expect(laneScopeOptionMetrics.bottomGap).toBeLessThanOrEqual(2);
|
||||
const minimumScopeOptionHeight = (page.viewportSize()?.width ?? 1024) < 640 ? 44 : 80;
|
||||
const minimumScopeOptionHeight = isCompactProject(testInfo) ? 44 : 80;
|
||||
expect(laneScopeOptionMetrics.optionHeight).toBeGreaterThanOrEqual(minimumScopeOptionHeight);
|
||||
await page.getByTestId("studio-scope-option-lane-7").click();
|
||||
await page.getByTestId("studio-scope-option-vehicle-8").click();
|
||||
|
||||
+2
-1
@@ -515,6 +515,7 @@ export function createApiProxyOptions(env = process.env) {
|
||||
export default defineConfig(({ mode }) => {
|
||||
const isProd = mode === 'production'
|
||||
const isPlaywrightRuntime = process.env.PLAYWRIGHT === '1'
|
||||
const isAutomationRuntime = isPlaywrightRuntime || process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true'
|
||||
|
||||
// Set COMMIT_HASH env var for use in the app
|
||||
const version = process.env.npm_package_version || '0.0.0'
|
||||
@@ -539,7 +540,7 @@ export default defineConfig(({ mode }) => {
|
||||
VueJsx(),
|
||||
releaseEntryManifest(),
|
||||
publicAssetAliases(),
|
||||
!isProd && !isPlaywrightRuntime && vueDevTools(),
|
||||
!isProd && !isAutomationRuntime && vueDevTools(),
|
||||
enableSingleFile && viteSingleFile(),
|
||||
VitePWA({
|
||||
registerType: 'autoUpdate',
|
||||
|
||||
Reference in New Issue
Block a user