Implement online board stale data timers
This commit is contained in:
@@ -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()}`;
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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 ({
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user