Stabilize cross-browser verification and upgrade rehearsal

This commit is contained in:
SimpleTest 2026-07-26 00:20:25 +03:00
parent 28940f9621
commit 9875fd7d05
7 changed files with 124 additions and 21 deletions

View File

@ -753,7 +753,7 @@ html {
}
.category-picker-placeholder {
color: color-mix(in oklab, var(--color-base-content) 48%, transparent);
color: color-mix(in oklab, var(--color-base-content) 70%, var(--color-base-100));
}
.category-picker-panel {

View File

@ -2115,6 +2115,7 @@ export const Hooks = {
this.mountMap = () => {
if (!this.mapElement) return
this.mapElement.dataset.mapIdle = "false"
const support = mapCanvasSupport()
if (!support.supported) {
@ -2149,6 +2150,12 @@ export const Hooks = {
this.map.touchZoomRotate.disableRotation()
this.map.addControl(new maplibregl.NavigationControl({showCompass: false}), "top-left")
this.map.on("dataloading", () => {
if (this.mapElement) this.mapElement.dataset.mapIdle = "false"
})
this.map.on("idle", () => {
if (this.mapElement) this.mapElement.dataset.mapIdle = "true"
})
this.map.on("load", () => {
this.mapLoaded = true

View File

@ -91,7 +91,9 @@ test("public navigation does not overflow narrow or wide viewports", async ({ br
await expect(page.getByRole("link", { name: "Activities", exact: true })).toBeVisible();
await expect(page.getByRole("link", { name: "Categories", exact: true })).toBeVisible();
await expect(page.getByRole("link", { name: "Helpers", exact: true })).toBeVisible();
await expect(page.getByRole("link", { name: "English", exact: true })).toBeVisible();
await expect(
page.locator("summary[aria-label='Language: English']:visible"),
).toBeVisible();
await expect(page.getByRole("link", { name: "Log in", exact: true })).toBeVisible();
const openDimensions = await page.evaluate(() => ({
@ -103,7 +105,9 @@ test("public navigation does not overflow narrow or wide viewports", async ({ br
} else {
await expect(page.locator("summary[aria-label='Primary navigation']")).toBeHidden();
await expect(page.getByRole("link", { name: "Requests", exact: true })).toBeVisible();
await expect(page.getByRole("link", { name: "English", exact: true })).toBeVisible();
await expect(
page.locator("summary[aria-label='Language: English']:visible"),
).toBeVisible();
await expect(page.getByRole("link", { name: "Register", exact: true })).toBeVisible();
await expect(page.getByRole("link", { name: "Log in", exact: true })).toBeVisible();
}

View File

@ -157,8 +157,22 @@ test("activity approval, privacy controls, reporting, and moderation work end to
await waitForMapReady(participant.page);
await gotoLiveView(participant.page, "/categories/proposals");
await participant.page.getByLabel("Proposed category").fill(proposalTitle);
await participant.page.getByLabel("Mode").selectOption("activity");
await participant.page
.getByLabel("Where should it appear?")
.selectOption("activity");
await participant.page
.locator("label.category-proposal-kind")
.filter({ hasText: "New subcategory" })
.click();
await expect(
participant.page.getByRole("radio", { name: /New subcategory/ }),
).toBeChecked();
await selectOptionContaining(
participant.page,
"Main category",
"Social activity",
);
await participant.page.getByLabel("Subcategory name").fill(proposalTitle);
await participant.page
.getByLabel("Why is this useful?")
.fill("A moderated public activity category for quiet board-game meetups.");
@ -247,8 +261,17 @@ test("activity approval, privacy controls, reporting, and moderation work end to
await expect(admin.page.getByText("User status updated.")).toBeVisible();
await gotoLiveView(participant.page, "/categories/proposals");
await participant.page.getByLabel("Proposed category").fill("E2E restricted proposal");
await participant.page.getByLabel("Mode").selectOption("help");
await participant.page
.getByLabel("Where should it appear?")
.selectOption("help");
await participant.page
.locator("label.category-proposal-kind")
.filter({ hasText: "New main category" })
.click();
await expect(
participant.page.getByRole("radio", { name: /New main category/ }),
).toBeChecked();
await participant.page.getByLabel("Category name").fill("E2E restricted proposal");
await participant.page
.getByLabel("Why is this useful?")
.fill("This must be rejected because the account is restricted.");

View File

@ -454,9 +454,14 @@ export async function loginWithPassword(
const form = page.locator("#login_form_password");
await page.getByText("Use a password instead", { exact: true }).click();
await expect(form).toBeVisible();
await form.getByLabel("Email").fill(email);
await form.getByLabel("Password").fill(password);
await expect(form.getByLabel("Password")).toHaveValue(password);
const emailInput = form.getByLabel("Email");
await emailInput.click();
await emailInput.pressSequentially(email);
await expect(emailInput).toHaveValue(email);
const passwordInput = form.getByLabel("Password");
await passwordInput.click();
await passwordInput.pressSequentially(password);
await expect(passwordInput).toHaveValue(password);
await form.getByRole("button", { name: "Log in only this time" }).click();
await expect(page).not.toHaveURL(/\/users\/log-in$/, { timeout: 10_000 });
await expect(page.getByText(email, { exact: true })).toBeVisible({
@ -493,18 +498,27 @@ export async function selectOptionContaining(
expectedText: string,
): Promise<void> {
await waitForLiveViewConnected(page);
const select = page.getByLabel(label);
const options = await select.locator("option").evaluateAll((nodes) =>
nodes.map((node) => ({
text: node.textContent?.trim() ?? "",
value: (node as HTMLOptionElement).value,
})),
);
const option = options.find(({ text }) => text.includes(expectedText));
const picker = page
.locator(".category-picker")
.filter({
has: page.locator(":scope > .label", { hasText: label }),
})
.first();
const source = picker.locator("[data-category-picker-source]");
const option = picker
.locator("[data-category-picker-option]")
.filter({ hasText: expectedText })
.first();
if (!option) {
throw new Error(`No ${label} option contains ${expectedText}`);
await expect(picker, `No category picker labelled ${label}`).toBeVisible();
await picker.locator("[data-category-picker-trigger]").click();
await expect(option, `No ${label} option contains ${expectedText}`).toBeVisible();
const value = await option.getAttribute("data-value");
if (!value) {
throw new Error(`${label} option ${expectedText} has no value`);
}
await select.selectOption(option.value);
await option.click();
await expect(source).toHaveValue(value);
}

View File

@ -59,6 +59,24 @@ test("nearby alerts, private notification inbox, preferences, and data export wo
await subscriptionForm
.locator('input[name="nearby_subscription[radius_meters]"][value="3000"]')
.check();
await expect
.poll(
async () => {
const map = subscriber.page.locator("#nearby-subscription-map");
if ((await map.getAttribute("data-map-idle")) === "true") {
return "idle";
}
if ((await map.locator("[data-map-fallback=true]").count()) === 1) {
return "fallback";
}
return null;
},
{ timeout: 15_000 },
)
.not.toBeNull();
await subscriptionForm.getByLabel("Email notification").check();
await subscriptionForm.getByRole("button", { name: "Create nearby alert" }).click();

View File

@ -148,6 +148,10 @@ cleanup() {
if [ "$status" -ne 0 ]; then
compose ps --all >"$output_dir/compose-ps.txt" 2>&1 || true
compose logs --no-color proxy \
>"$output_dir/proxy.log" 2>&1 || true
compose logs --no-color docker-api-proxy \
>"$output_dir/docker-api-proxy.log" 2>&1 || true
fi
if ! compose down --volumes --remove-orphans >/dev/null 2>&1; then
@ -349,6 +353,39 @@ if [ -z "$web_container" ]; then
exit 1
fi
# Docker Compose considers a running proxy without a container healthcheck
# ready before Traefik necessarily applies its first Docker-provider snapshot.
# Wait for the application route itself so the assertions below measure the
# configured router rather than that short provider-initialisation window.
route_wait_attempts=30
route_wait_attempt=1
route_status=000
while [ "$route_wait_attempt" -le "$route_wait_attempts" ]; do
route_status=$(
curl \
--silent \
--show-error \
--output /dev/null \
--write-out '%{http_code}' \
--header "Host: $PHX_HOST" \
"http://127.0.0.1:$proxy_port/healthz/ready" ||
printf '000'
)
if [ "$route_status" = "200" ]; then
break
fi
sleep 1
route_wait_attempt=$((route_wait_attempt + 1))
done
if [ "$route_status" != "200" ]; then
echo "The isolated proxy route did not become ready after $route_wait_attempts checks; last HTTP status: $route_status." >&2
exit 1
fi
case "$PHX_SCHEME:$PHX_URL_PORT" in
http:80 | https:443)
public_origin="$PHX_SCHEME://$PHX_HOST"