Stabilize cross-browser verification and upgrade rehearsal
This commit is contained in:
parent
28940f9621
commit
9875fd7d05
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user