Implement online board stale data timers

This commit is contained in:
2026-05-18 18:28:40 +03:00
parent cac3846657
commit f1ab656305
24 changed files with 323 additions and 10 deletions
+4
View File
@@ -9,6 +9,10 @@ export function formatYmd(date: Date): string {
return `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}`;
}
export function formatIsoDate(date: Date): string {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
}
export function formatRuDate(date: Date): string {
return `${String(date.getDate()).padStart(2, "0")}.${String(date.getMonth() + 1).padStart(2, "0")}.${date.getFullYear()}`;
}
+57
View File
@@ -0,0 +1,57 @@
import { addDays, formatIsoDate, formatYmd } from "./dates";
type DateTimeValue = {
utc?: string;
local?: string;
localTime?: string;
};
type OnlineboardDetailsFixture = {
data: {
routes: Array<{
flightId: {
dateLT?: string;
date: string;
};
leg: {
departure: { times: Record<string, DateTimeValue> };
arrival: { times: Record<string, DateTimeValue> };
daysForTabs?: string[];
};
}>;
};
};
function replaceDatePart(value: string | undefined, isoDate: string): string | undefined {
return value?.replace(/^\d{4}-\d{2}-\d{2}/, isoDate);
}
export function nextOnlineboardDetailsFixture(raw: string): {
body: string;
compactDate: string;
} {
const date = addDays(new Date(), 1);
const isoDate = formatIsoDate(date);
const compactDate = formatYmd(date);
const fixture = JSON.parse(raw) as OnlineboardDetailsFixture;
for (const route of fixture.data.routes) {
route.flightId.date = isoDate;
route.flightId.dateLT = isoDate;
for (const point of [route.leg.departure, route.leg.arrival]) {
for (const value of Object.values(point.times)) {
const utc = replaceDatePart(value.utc, isoDate);
if (utc !== undefined) value.utc = utc;
const local = replaceDatePart(value.local, isoDate);
if (local !== undefined) value.local = local;
}
}
route.leg.daysForTabs = Array.from({ length: 15 }, (_, index) =>
formatYmd(addDays(date, index)),
);
}
return { body: JSON.stringify(fixture), compactDate };
}
+49
View File
@@ -264,6 +264,55 @@ test.describe("Online Board", () => {
await expect(arrInput).toHaveValue("Самара");
});
test("route results show stale-data overlay after inactivity timeout", async ({
page,
consoleMessages,
}) => {
await page.addInitScript(() => {
const target = window as unknown as { __ENV__?: Record<string, string> };
target.__ENV__ = {
...(target.__ENV__ ?? {}),
REFRESH_PAUSE_MIN: "0.01",
REFRESH_STOP_MIN: "1",
};
});
await routeDictionaryFixtures(page);
await routeOnlineboardRouteFixtures(page);
await page.goto(`/ru/onlineboard/route/MOW-KUF-${formatYmd(new Date())}`);
await expect(page.locator('[data-testid="online-board-search"]')).toBeVisible({
timeout: 10000,
});
await expect(page.locator('[data-testid="stale-data-overlay"]')).toHaveText(
"Данные устарели, обновите страницу!",
{ timeout: 2000 },
);
});
test("route results redirect to main page after stale-data stop timeout", async ({
page,
consoleMessages,
}) => {
await page.addInitScript(() => {
const target = window as unknown as { __ENV__?: Record<string, string> };
target.__ENV__ = {
...(target.__ENV__ ?? {}),
REFRESH_PAUSE_MIN: "0.001",
REFRESH_STOP_MIN: "0.02",
};
});
await routeDictionaryFixtures(page);
await routeOnlineboardRouteFixtures(page);
await page.goto(`/ru/onlineboard/route/MOW-KUF-${formatYmd(new Date())}`);
await expect(page.locator('[data-testid="online-board-search"]')).toBeVisible({
timeout: 10000,
});
await page.waitForURL(/\/(ru\/onlineboard)?$/, { timeout: 4000 });
});
// Requires live API (calendar days endpoint).
// Skipped when WAF blocks flights.test.aeroflot.ru.
test.skip("route search results page shows calendar strip with day numbers", async ({
+5 -2
View File
@@ -2,6 +2,7 @@ import { test, expect } from "./fixtures/console-gate";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { nextOnlineboardDetailsFixture } from "./helpers/onlineboard-fixtures";
// TIRREDESIGN-24 — in the online-board flight details card, the aircraft
// title under "Борт" must be a clickable external link that opens in a new tab.
@@ -22,11 +23,13 @@ test("Onlineboard details aircraft title opens Aeroflot plane park in a new tab"
context,
consoleMessages,
}) => {
const details = nextOnlineboardDetailsFixture(onlineboardDetails);
await page.route("**/api/flights/v1.1/ru/onlineboard/details?**", async (route) => {
await route.fulfill({
status: 200,
contentType: "application/json",
body: onlineboardDetails,
body: details.body,
});
});
@@ -38,7 +41,7 @@ test("Onlineboard details aircraft title opens Aeroflot plane park in a new tab"
});
});
await page.goto("/ru-ru/onlineboard/SU6951-20260514");
await page.goto(`/ru-ru/onlineboard/SU6951-${details.compactDate}`);
const aircraftRow = page.locator('[data-testid="details-row-aircraft"]');
await expect(aircraftRow).toBeVisible({ timeout: 15000 });
@@ -2,6 +2,7 @@ import { test, expect } from "./fixtures/console-gate";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { nextOnlineboardDetailsFixture } from "./helpers/onlineboard-fixtures";
const FIXTURE_DIR = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
@@ -16,24 +17,26 @@ test("TIRREDESIGN-7: onlineboard details shows non-scheduled transition even whe
page,
consoleMessages,
}) => {
const details = structuredClone(baseDetails);
const shifted = nextOnlineboardDetailsFixture(JSON.stringify(baseDetails));
const details = JSON.parse(shifted.body);
const flight = details.data.routes[0];
const leg = flight.leg;
const isoDate = `${shifted.compactDate.slice(0, 4)}-${shifted.compactDate.slice(4, 6)}-${shifted.compactDate.slice(6, 8)}`;
flight.status = "InFlight";
leg.status = "InFlight";
leg.transition = {
registration: {
start: {
utc: "2026-05-14T07:00:00Z",
local: "2026-05-14T10:00:00+03:00",
utc: `${isoDate}T07:00:00Z`,
local: `${isoDate}T10:00:00+03:00`,
dayChange: { value: 0, title: "" },
localTime: "10:00",
tzOffset: 180,
},
end: {
utc: "2026-05-14T07:30:00Z",
local: "2026-05-14T10:30:00+03:00",
utc: `${isoDate}T07:30:00Z`,
local: `${isoDate}T10:30:00+03:00`,
dayChange: { value: 0, title: "" },
localTime: "10:30",
tzOffset: 180,
@@ -51,7 +54,7 @@ test("TIRREDESIGN-7: onlineboard details shows non-scheduled transition even whe
});
});
await page.goto("/ru-ru/onlineboard/SU6951-20260514");
await page.goto(`/ru-ru/onlineboard/SU6951-${shifted.compactDate}`);
const registrationRow = page.locator('[data-testid="details-row-registration"]');
await expect(registrationRow).toBeVisible({ timeout: 15000 });
@@ -52,7 +52,6 @@ test("TIRREDESIGN-26: schedule details day tabs disable non-operating flight dat
await expect(page.getByTestId("day-tabs")).toBeVisible({ timeout: 15000 });
await expect(page.getByTestId("day-tab-20260519")).toBeEnabled();
await page.getByTestId("day-tabs-next").click();
const nonOperatingFriday = page.getByTestId("day-tab-20260522");
await expect(nonOperatingFriday).toBeDisabled();
await expect(page.getByTestId("day-tab-20260523")).toBeEnabled();
@@ -12,6 +12,26 @@ import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
test("schedule route page surfaces the buy ticket button inside an expanded flight body", async ({
page,
}) => {
await page.addInitScript(() => {
const fixedTime = new Date("2026-05-17T00:00:00+03:00").getTime();
const RealDate = Date;
const FixedDate = function fixedDate(
this: Date,
...args: unknown[]
) {
return args.length === 0
? new RealDate(fixedTime)
: Reflect.construct(RealDate, args);
} as unknown as DateConstructor;
FixedDate.now = () => fixedTime;
FixedDate.parse = RealDate.parse;
FixedDate.UTC = RealDate.UTC;
Object.defineProperty(FixedDate, "prototype", {
value: RealDate.prototype,
});
Object.setPrototypeOf(FixedDate, RealDate);
window.Date = FixedDate;
});
await routeScheduleVvoMjzFixtures(page);
// Pick a calendar week well clear of the 2h pre-departure cutoff so every
// flight in the list is inside the buy window. Earlier-this-week URLs hit