Stabilize schedule e2e date fixtures

This commit is contained in:
2026-05-28 11:56:40 +03:00
parent 5c309004f0
commit 5e33debfb4
17 changed files with 224 additions and 65 deletions
@@ -155,6 +155,22 @@ describe("DayTabs", () => {
expect(screen.getByTestId("day-select")).toBeTruthy();
});
it("deduplicates available dates before rendering the mobile select", () => {
render(
<DayTabs
selectedDate="20260416"
availableDates={["20260416", "20260416", "20260417"]}
daysBefore={1}
daysAfter={2}
locale="en"
onNavigate={() => {}}
/>,
);
const options = screen.getByTestId("day-select").querySelectorAll("option");
expect(options).toHaveLength(2);
});
// ── TZ §4.1.13.1 compliance tests ──────────────────────────────────────
it("4.1.13.1: active range today-1 to today+14 yields 16 total dates", () => {
@@ -68,8 +68,15 @@ export const DayTabs: FC<DayTabsProps> = ({
// the route has no day data). Treat every date as tappable in that
// case — matches Angular where the tabs stay enabled until we *know*
// the upstream reports no flights for a given day.
const availableSet = useMemo(() => new Set(availableDates), [availableDates]);
const disableByAvailability = availableDates.length > 0;
const uniqueAvailableDates = useMemo(
() => Array.from(new Set(availableDates)),
[availableDates],
);
const availableSet = useMemo(
() => new Set(uniqueAvailableDates),
[uniqueAvailableDates],
);
const disableByAvailability = uniqueAvailableDates.length > 0;
const visibleDates = allDates.slice(
currentPage * PAGE_SIZE,
@@ -150,7 +157,7 @@ export const DayTabs: FC<DayTabsProps> = ({
</div>
<DaySelect
selectedDate={selectedDate}
availableDates={availableDates}
availableDates={uniqueAvailableDates}
locale={locale}
onNavigate={onNavigate}
{...(mobileCaptionKey ? { captionKey: mobileCaptionKey } : {})}
+9 -4
View File
@@ -2,6 +2,7 @@ import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import type { Page, Route } from "@playwright/test";
import { replaceVvoMjzFixtureDates } from "./dates";
const FIXTURE_DIR = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
@@ -12,6 +13,10 @@ function fixtureText(name: string): string {
return fs.readFileSync(path.join(FIXTURE_DIR, name), "utf8");
}
function vvoMjzFixtureText(name: string): string {
return replaceVvoMjzFixtureDates(fixtureText(name));
}
async function fulfillJson(route: Route, body: string): Promise<void> {
await route.fulfill({
status: 200,
@@ -96,10 +101,10 @@ export async function routeScheduleVvoMjzFixtures(page: Page): Promise<void> {
fulfillJson(route, fixtureText("schedule-days-route.json")),
);
await page.route("**/api/flights/1/*/schedule?**", (route) =>
fulfillJson(route, fixtureText("schedule-search-vvo-mjz.json")),
fulfillJson(route, vvoMjzFixtureText("schedule-search-vvo-mjz.json")),
);
await page.route("**/api/flights/v1.1/*/schedule/details?**", (route) =>
fulfillJson(route, fixtureText("schedule-details-vvo-mjz.json")),
fulfillJson(route, vvoMjzFixtureText("schedule-details-vvo-mjz.json")),
);
}
@@ -112,10 +117,10 @@ export async function routeScheduleVvoMjzServicesFixtures(
fulfillJson(route, fixtureText("schedule-days-route.json")),
);
await page.route("**/api/flights/1/*/schedule?**", (route) =>
fulfillJson(route, fixtureText("schedule-search-vvo-mjz.json")),
fulfillJson(route, vvoMjzFixtureText("schedule-search-vvo-mjz.json")),
);
await page.route("**/api/flights/v1.1/*/schedule/details?**", (route) => {
const fixture = JSON.parse(fixtureText("schedule-details-vvo-mjz.json"));
const fixture = JSON.parse(vvoMjzFixtureText("schedule-details-vvo-mjz.json"));
const actual = fixture.data.routes[0].leg.equipment.aircraft.actual;
actual.onBoardServices = [
{
+79
View File
@@ -32,3 +32,82 @@ export function sundayOfWeek(date: Date): Date {
export function formatRuWeekRange(date: Date): string {
return `${formatRuDate(mondayOfWeek(date))} - ${formatRuDate(sundayOfWeek(date))}`;
}
export function vvoMjzScheduleDates(): {
start: Date;
end: Date;
leg1: Date;
leg2: Date;
altLeg1: Date;
altLeg2: Date;
preStart: Date;
startYmd: string;
endYmd: string;
leg1Ymd: string;
leg2Ymd: string;
altLeg1Ymd: string;
altLeg2Ymd: string;
leg1Iso: string;
leg2Iso: string;
altLeg1Iso: string;
altLeg2Iso: string;
} {
const start = mondayOfWeek(addDays(new Date(), 7));
const end = sundayOfWeek(start);
const leg1 = start;
const leg2 = addDays(start, 1);
const altLeg1 = addDays(start, 4);
const altLeg2 = addDays(start, 5);
const preStart = addDays(start, -2);
return {
start,
end,
leg1,
leg2,
altLeg1,
altLeg2,
preStart,
startYmd: formatYmd(start),
endYmd: formatYmd(end),
leg1Ymd: formatYmd(leg1),
leg2Ymd: formatYmd(leg2),
altLeg1Ymd: formatYmd(altLeg1),
altLeg2Ymd: formatYmd(altLeg2),
leg1Iso: formatIsoDate(leg1),
leg2Iso: formatIsoDate(leg2),
altLeg1Iso: formatIsoDate(altLeg1),
altLeg2Iso: formatIsoDate(altLeg2),
};
}
export function vvoMjzRouteUrl(route = "VVO-MJZ"): string {
const dates = vvoMjzScheduleDates();
return `/ru-ru/schedule/route/${route}-${dates.startYmd}-${dates.endYmd}`;
}
export function vvoMjzDetailsUrl(): string {
const dates = vvoMjzScheduleDates();
return `/ru-ru/schedule/VVO/SU5752-${dates.leg1Ymd}/KJA/SU6837-${dates.leg2Ymd}/MJZ?request=schedule-route-VVO-MJZ-${dates.startYmd}-${dates.endYmd}`;
}
export function replaceVvoMjzFixtureDates(text: string): string {
const dates = vvoMjzScheduleDates();
const replacements: Array<[string, string]> = [
["20260518", dates.leg1Ymd],
["20260519", dates.leg2Ymd],
["20260522", dates.altLeg1Ymd],
["20260523", dates.altLeg2Ymd],
["20260524", dates.endYmd],
["2026-05-18", dates.leg1Iso],
["2026-05-19", dates.leg2Iso],
["2026-05-22", dates.altLeg1Iso],
["2026-05-23", dates.altLeg2Iso],
["2026-05-24", formatIsoDate(dates.end)],
];
return replacements.reduce(
(result, [from, to]) => result.replaceAll(from, to),
text,
);
}
+14
View File
@@ -1,10 +1,22 @@
import { test, expect } from "./fixtures/console-gate";
import {
routeAppSettingsFixture,
routeDictionaryFixtures,
routePopularRequestsFixture,
} from "./helpers/api-fixtures";
async function routeNavigationStartFixtures(page: import("@playwright/test").Page): Promise<void> {
await routeAppSettingsFixture(page);
await routeDictionaryFixtures(page);
await routePopularRequestsFixture(page);
}
test.describe("Cross-feature navigation", () => {
test("locale switching: /ru/onlineboard -> /en/onlineboard shows English content", async ({
page,
consoleMessages,
}) => {
await routeNavigationStartFixtures(page);
// Start on Russian online board
await page.goto("/ru/onlineboard");
await page.waitForLoadState("domcontentloaded");
@@ -25,6 +37,7 @@ test.describe("Cross-feature navigation", () => {
});
test("error page: /error/404 renders 404 content", async ({ page, consoleMessages }) => {
await routeNavigationStartFixtures(page);
// Navigate to a working page first, then client-side navigate to the error
// page. Direct URL navigation to /error/404 renders blank because the
// error route is outside [lang]/layout.tsx and SSR produces empty output.
@@ -45,6 +58,7 @@ test.describe("Cross-feature navigation", () => {
page,
consoleMessages,
}) => {
await routeNavigationStartFixtures(page);
// Navigate to a working page first, then client-side navigate to the error
// page (same reason as the 404 test above).
await page.goto("/ru/onlineboard");
+8 -6
View File
@@ -2,6 +2,10 @@ import { test, expect } from "./fixtures/console-gate";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
replaceVvoMjzFixtureDates,
vvoMjzDetailsUrl,
} from "./helpers/dates";
// Schedule details uses the same Angular aircraft-link behavior as
// online-board: the model text under "Борт" opens the generic plane park page.
@@ -14,9 +18,7 @@ const scheduleDetails = fs.readFileSync(
path.join(FIXTURE_DIR, "schedule-details.json"),
"utf8",
);
const URL =
"/ru-ru/schedule/VVO/SU5752-20260518/KJA/SU6837-20260519/MJZ?request=schedule-route-VVO-MJZ-20260518-20260524";
const scheduleDetailsBody = replaceVvoMjzFixtureDates(scheduleDetails);
test("Schedule details aircraft title opens Aeroflot plane park in a new tab", async ({
page,
@@ -27,11 +29,11 @@ test("Schedule details aircraft title opens Aeroflot plane park in a new tab", a
await route.fulfill({
status: 200,
contentType: "application/json",
body: scheduleDetails,
body: scheduleDetailsBody,
});
});
await context.route("http://www.aeroflot.ru/cms/ru/flight/plane_park", async (route) => {
await context.route(/https?:\/\/www\.aeroflot\.ru\/cms\/ru\/flight\/plane_park/, async (route) => {
await route.fulfill({
status: 200,
contentType: "text/html",
@@ -39,7 +41,7 @@ test("Schedule details aircraft title opens Aeroflot plane park in a new tab", a
});
});
await page.goto(URL);
await page.goto(vvoMjzDetailsUrl());
const details = page.locator(".schedule-leg-details");
await expect(details).toHaveCount(1, { timeout: 15000 });
+13 -3
View File
@@ -1,4 +1,8 @@
import { test, expect } from "./fixtures/console-gate";
import {
routeAppSettingsFixture,
routeDictionaryFixtures,
} from "./helpers/api-fixtures";
import {
addDays,
formatRuDate,
@@ -21,6 +25,8 @@ test.describe("Schedule date-range picker (week-snap)", () => {
page,
consoleMessages,
}) => {
await routeAppSettingsFixture(page);
await routeDictionaryFixtures(page);
await page.goto("/ru-ru/schedule");
await expect(page.getByTestId("date-range-input")).toBeVisible({
timeout: 15000,
@@ -31,21 +37,23 @@ test.describe("Schedule date-range picker (week-snap)", () => {
const panel = page.locator(".p-datepicker-panel, .p-datepicker").first();
await expect(panel).toBeVisible();
const target = addDays(new Date(), 7);
const target = addDays(new Date(), 1);
await panel.locator(`td[aria-label="${formatRuDate(target)}"] span`).click();
// Panel auto-dismissed.
await expect(panel).toBeHidden({ timeout: 5000 });
// Input now holds the full week range.
// The current week is rendered with Angular's compact label.
const input = page.locator("#schedule-date-from");
await expect(input).toHaveValue(formatRuWeekRange(target));
await expect(input).toHaveValue("Текущая неделя");
});
test("clicking an enabled other-month bleed-in day snaps to its Mon-Sun week", async ({
page,
consoleMessages,
}) => {
await routeAppSettingsFixture(page);
await routeDictionaryFixtures(page);
await page.goto("/ru-ru/schedule");
await expect(page.getByTestId("date-range-input")).toBeVisible({
timeout: 15000,
@@ -83,6 +91,8 @@ test.describe("Schedule date-range picker (week-snap)", () => {
});
test("input renders as range placeholder when empty", async ({ page, consoleMessages }) => {
await routeAppSettingsFixture(page);
await routeDictionaryFixtures(page);
await page.goto("/ru-ru/schedule");
const input = page.locator("#schedule-date-from");
await expect(input).toHaveAttribute(
@@ -1,5 +1,6 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
import { vvoMjzRouteUrl } from "./helpers/dates";
// When the user clicks a connecting itinerary in the Schedule list, the
// resulting flight-details URL must include EVERY leg, not just the
@@ -14,7 +15,7 @@ test("connecting itinerary navigates to a multi-segment URL with both legs rende
consoleMessages,
}) => {
await routeScheduleVvoMjzFixtures(page);
await page.goto("/ru-ru/schedule/route/VVO-MJZ-20260518-20260524");
await page.goto(vvoMjzRouteUrl());
await expect(page.locator(".flight-card").first()).toBeVisible({
timeout: 15000,
});
@@ -3,23 +3,28 @@ import path from "node:path";
import { fileURLToPath } from "node:url";
import { test, expect } from "./fixtures/console-gate";
import { routeAppSettingsFixture } from "./helpers/api-fixtures";
import {
replaceVvoMjzFixtureDates,
vvoMjzScheduleDates,
} from "./helpers/dates";
const FIXTURE_DIR = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
"../fixtures/api",
);
const URL =
"/ru-ru/schedule/KJA/SU6837-20260519/MJZ?request=schedule-route-KJA-MJZ-20260518-20260524-C0";
test("TIRREDESIGN-26: schedule details day tabs disable non-operating flight dates", async ({
page,
consoleMessages,
}) => {
const dates = vvoMjzScheduleDates();
const url = `/ru-ru/schedule/KJA/SU6837-${dates.leg2Ymd}/MJZ?request=schedule-route-KJA-MJZ-${dates.startYmd}-${dates.endYmd}-C0`;
await routeAppSettingsFixture(page);
await page.route("**/api/flights/v1.1/*/schedule/details?**", async (route) => {
const source = JSON.parse(
replaceVvoMjzFixtureDates(
fs.readFileSync(path.join(FIXTURE_DIR, "schedule-details-vvo-mjz.json"), "utf8"),
),
) as {
data: {
routes: Array<{
@@ -32,7 +37,7 @@ test("TIRREDESIGN-26: schedule details day tabs disable non-operating flight dat
(flight) =>
flight.flightId.carrier === "SU" &&
flight.flightId.flightNumber === "6837" &&
flight.flightId.date === "2026-05-19",
flight.flightId.date === dates.leg2Iso,
);
expect(su6837).toBeTruthy();
await route.fulfill({
@@ -42,18 +47,19 @@ test("TIRREDESIGN-26: schedule details day tabs disable non-operating flight dat
data: {
partners: [],
routes: su6837 ? [su6837] : [],
daysOfFlight: ["20260519", "20260523"],
daysOfFlight: [dates.leg2Ymd, dates.altLeg2Ymd],
},
}),
});
});
await page.goto(URL);
await page.goto(url);
await expect(page.getByTestId("day-tabs")).toBeVisible({ timeout: 15000 });
await page.getByTestId("day-tabs-next").click();
const nonOperatingFriday = page.getByTestId("day-tab-20260522");
const nonOperatingFriday = page.getByTestId(`day-tab-${dates.altLeg1Ymd}`);
await expect(nonOperatingFriday).toBeDisabled();
await expect(page.getByTestId("day-tab-20260523")).toBeEnabled();
await expect(page.getByTestId(`day-tab-${dates.altLeg2Ymd}`)).toBeEnabled();
await expect(page.getByTestId("schedule-details-not-found")).toHaveCount(0);
});
@@ -1,5 +1,6 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
import { vvoMjzDetailsUrl } from "./helpers/dates";
// Schedule Details "Питание на борту" must render meal-class sub-icons
// (Эконом класс / Комфорт класс / Бизнес класс) ONLY when the API
@@ -11,15 +12,12 @@ import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
// Reference URL covers a connecting itinerary where both live legs
// currently return Economy / Comfort / Business meal entries.
const URL =
"/ru-ru/schedule/VVO/SU5752-20260518/KJA/SU6837-20260519/MJZ?request=schedule-route-VVO-MJZ-20260518-20260524";
test("Питание sub-icons appear only for legs whose API meal[] contains them", async ({
page,
consoleMessages,
}) => {
await routeScheduleVvoMjzFixtures(page);
await page.goto(URL);
await page.goto(vvoMjzDetailsUrl());
// Wait until both leg-details panels are mounted.
await expect(page.locator(".schedule-leg-details")).toHaveCount(2, {
@@ -1,5 +1,6 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
import { vvoMjzDetailsUrl } from "./helpers/dates";
// On the schedule details page the left mini-list renders a SINGLE
// card for the currently-open flight — matching Angular's
@@ -12,12 +13,9 @@ import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
// flight numbers ("SU 5752, SU 6837") and the combined
// Vladivostok→Mirny origin/destination, not just the first leg.
const URL =
"/ru-ru/schedule/VVO/SU5752-20260518/KJA/SU6837-20260519/MJZ?request=schedule-route-VVO-MJZ-20260518-20260524";
test("mini-list — one combined card for the open SU 5752+SU 6837 itinerary", async ({ page, consoleMessages }) => {
await routeScheduleVvoMjzFixtures(page);
await page.goto(URL);
await page.goto(vvoMjzDetailsUrl());
const miniList = page.locator(".schedule-mini-list");
await expect(miniList).toBeVisible({ timeout: 15000 });
@@ -1,14 +1,12 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzServicesFixtures } from "./helpers/api-fixtures";
const URL =
"/ru-ru/schedule/VVO/SU5752-20260518/KJA/SU6837-20260519/MJZ?request=schedule-route-VVO-MJZ-20260518-20260524";
import { vvoMjzDetailsUrl } from "./helpers/dates";
test("schedule details render onboard services from actual aircraft data", async ({
page,
}) => {
await routeScheduleVvoMjzServicesFixtures(page);
await page.goto(URL);
await page.goto(vvoMjzDetailsUrl());
const leg1 = page.locator(".schedule-leg-details").nth(0);
await expect(leg1).toBeVisible({ timeout: 15000 });
@@ -1,5 +1,6 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
import { vvoMjzDetailsUrl } from "./helpers/dates";
// Schedule details page must render Angular's `<schedule-details-header>`
// summary block between the day-tabs strip and the per-leg cards:
@@ -13,12 +14,9 @@ import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
// This test pins those guarantees so the page can't regress to "no
// summary header" or "first-leg-only badge / raw ISO timeline" again.
const URL =
"/ru-ru/schedule/VVO/SU5752-20260518/KJA/SU6837-20260519/MJZ?request=schedule-route-VVO-MJZ-20260518-20260524";
test("summary header — both badges + last-update + formatted full-route timeline", async ({ page, consoleMessages }) => {
await routeScheduleVvoMjzFixtures(page);
await page.goto(URL);
await page.goto(vvoMjzDetailsUrl());
const summary = page.locator(".schedule-details__summary");
await expect(summary).toBeVisible({ timeout: 15000 });
+8 -2
View File
@@ -1,11 +1,15 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
import { vvoMjzRouteUrl, vvoMjzScheduleDates } from "./helpers/dates";
test.describe("Schedule results filter", () => {
test("changed route criteria can be submitted immediately after a previous search", async ({
page,
consoleMessages,
}) => {
await page.goto("/ru-ru/schedule/route/VVO-MJZ-20260518-20260524");
await routeScheduleVvoMjzFixtures(page);
const dates = vvoMjzScheduleDates();
await page.goto(vvoMjzRouteUrl());
await expect(page.locator("h1")).toContainText(/Владивосток.*Мирный|VVO.*MJZ/, {
timeout: 30000,
@@ -29,7 +33,9 @@ test.describe("Schedule results filter", () => {
);
await submit.click();
await expect(page).toHaveURL(/\/ru-ru\/schedule\/route\/MJZ-VVO-20260518-20260524/);
await expect(page).toHaveURL(
new RegExp(`/ru-ru/schedule/route/MJZ-VVO-${dates.startYmd}-${dates.endYmd}`),
);
expect((await scheduleResponse).status()).toBe(200);
expect(consoleMessages).toEqual([]);
});
@@ -1,7 +1,6 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
const ROUTE_URL = "/ru-ru/schedule/route/VVO-MJZ-20260518-20260524";
import { vvoMjzRouteUrl, vvoMjzScheduleDates } from "./helpers/dates";
test.describe("Schedule flight details button", () => {
test.beforeEach(async ({ page }) => {
@@ -11,7 +10,7 @@ test.describe("Schedule flight details button", () => {
test("flight details button is visible in expanded flight body", async ({
page,
}) => {
await page.goto(ROUTE_URL);
await page.goto(vvoMjzRouteUrl());
const cards = page.locator(".flight-card--clickable");
await expect(cards.first()).toBeVisible({ timeout: 30000 });
@@ -26,7 +25,7 @@ test.describe("Schedule flight details button", () => {
});
test("flight details button has correct label (Russian)", async ({ page }) => {
await page.goto(ROUTE_URL);
await page.goto(vvoMjzRouteUrl());
const cards = page.locator(".flight-card--clickable");
await expect(cards.first()).toBeVisible({ timeout: 30000 });
@@ -42,7 +41,7 @@ test.describe("Schedule flight details button", () => {
test("flight details button navigates to flight details page", async ({
page,
}) => {
await page.goto(ROUTE_URL);
await page.goto(vvoMjzRouteUrl());
const cards = page.locator(".flight-card--clickable");
await expect(cards.first()).toBeVisible({ timeout: 30000 });
@@ -58,7 +57,7 @@ test.describe("Schedule flight details button", () => {
test("flight details button works for connecting flights", async ({
page,
}) => {
await page.goto(ROUTE_URL);
await page.goto(vvoMjzRouteUrl());
const cards = page.locator(".flight-card--clickable");
await expect(cards.first()).toBeVisible({ timeout: 30000 });
@@ -78,7 +77,8 @@ test.describe("Schedule flight details button", () => {
test("flight details button preserves search context in URL", async ({
page,
}) => {
await page.goto(ROUTE_URL);
const dates = vvoMjzScheduleDates();
await page.goto(vvoMjzRouteUrl());
const cards = page.locator(".flight-card--clickable");
await expect(cards.first()).toBeVisible({ timeout: 30000 });
@@ -90,6 +90,6 @@ test.describe("Schedule flight details button", () => {
const url = page.url();
expect(url).toContain("?request=");
expect(url).toContain("schedule-route-VVO-MJZ-20260518-20260524");
expect(url).toContain(`schedule-route-VVO-MJZ-${dates.startYmd}-${dates.endYmd}`);
});
});
+2 -1
View File
@@ -1,5 +1,6 @@
import { test, expect } from "./fixtures/console-gate";
import { routeScheduleVvoMjzFixtures } from "./helpers/api-fixtures";
import { vvoMjzRouteUrl } from "./helpers/dates";
// Schedule search-results page must mirror Angular's
// `<flight-details-body-actions>` strip in each expanded flight body —
@@ -37,7 +38,7 @@ test("schedule route page surfaces the buy ticket button inside an expanded flig
// flight in the list is inside the buy window. Earlier-this-week URLs hit
// a "today's first flight is < 2h out" edge case and the buy button hides
// on that single row, even though the rest of the list shows it.
await page.goto("/ru-ru/schedule/route/VVO-MJZ-20260518-20260524");
await page.goto(vvoMjzRouteUrl());
// Wait for the list to render.
const cards = page.locator(".flight-card--clickable");
+32 -12
View File
@@ -3,13 +3,26 @@ import {
routeAppSettingsFixture,
routeDictionaryFixtures,
} from "./helpers/api-fixtures";
import {
addDays,
formatIsoDate,
formatYmd,
vvoMjzScheduleDates,
} from "./helpers/dates";
// TIRREDESIGN-28: SU0634 KJA -> HKT departs after midnight local time while
// the backend flightId.date remains on the previous service day. Angular builds
// the schedule details URL from the leg's local scheduled departure date, so
// the details request receives 2026-05-19 and the aircraft link is rendered.
// the details request receives the local departure date and the aircraft link is rendered.
const ROUTE_URL = "/ru-ru/schedule/route/KJA-HKT-20260519-20260525-C0";
const dates = vvoMjzScheduleDates();
const serviceDate = dates.leg1;
const localDate = dates.leg2;
const routeEnd = addDays(localDate, 6);
const SERVICE_DATE_ISO = formatIsoDate(serviceDate);
const LOCAL_DATE_ISO = formatIsoDate(localDate);
const LOCAL_DATE_YMD = formatYmd(localDate);
const ROUTE_URL = `/ru-ru/schedule/route/KJA-HKT-${LOCAL_DATE_YMD}-${formatYmd(routeEnd)}-C0`;
const PLANE_PARK_URL = "http://www.aeroflot.ru/cms/ru/flight/plane_park";
const su0634Search = [
@@ -22,8 +35,8 @@ const su0634Search = [
carrier: "SU",
flightNumber: "0634",
suffix: "",
date: "2026-05-18",
dateLT: "2026-05-19",
date: SERVICE_DATE_ISO,
dateLT: LOCAL_DATE_ISO,
},
flyingTime: "08:10:00",
leg: {
@@ -38,8 +51,8 @@ const su0634Search = [
terminal: "1",
times: {
scheduledDeparture: {
utc: "2026-05-18T22:05:00Z",
local: "2026-05-19T05:05:00+07:00",
utc: `${SERVICE_DATE_ISO}T22:05:00Z`,
local: `${LOCAL_DATE_ISO}T05:05:00+07:00`,
dayChange: { value: 0, title: "" },
localTime: "05:05",
tzOffset: 420,
@@ -57,8 +70,8 @@ const su0634Search = [
terminal: "I",
times: {
scheduledArrival: {
utc: "2026-05-19T06:15:00Z",
local: "2026-05-19T13:15:00+07:00",
utc: `${LOCAL_DATE_ISO}T06:15:00Z`,
local: `${LOCAL_DATE_ISO}T13:15:00+07:00`,
dayChange: { value: 0, title: "" },
localTime: "13:15",
tzOffset: 420,
@@ -100,7 +113,12 @@ const su0634Details = {
data: {
routes: su0634Search,
partners: [],
daysOfFlight: ["20260515", "20260519", "20260522", "20260526"],
daysOfFlight: [
formatYmd(addDays(localDate, -4)),
LOCAL_DATE_YMD,
formatYmd(addDays(localDate, 3)),
formatYmd(addDays(localDate, 7)),
],
},
};
@@ -132,13 +150,13 @@ test("TIRREDESIGN-28: SU0634 schedule details uses local date and opens plane pa
status: 200,
contentType: "application/json",
body: JSON.stringify(
date === "2026-05-19T00:00:00"
date === `${LOCAL_DATE_ISO}T00:00:00`
? su0634Details
: { data: { routes: [], partners: [], daysOfFlight: [] } },
),
});
});
await context.route(PLANE_PARK_URL, async (route) => {
await context.route(/https?:\/\/www\.aeroflot\.ru\/cms\/ru\/flight\/plane_park/, async (route) => {
await route.fulfill({
status: 200,
contentType: "text/html",
@@ -156,7 +174,9 @@ test("TIRREDESIGN-28: SU0634 schedule details uses local date and opens plane pa
await expect(detailsBtn).toBeVisible({ timeout: 10000 });
await detailsBtn.click();
await expect(page).toHaveURL(/\/ru-ru\/schedule\/KJA\/SU0634-20260519\/HKT/);
await expect(page).toHaveURL(
new RegExp(`/ru-ru/schedule/KJA/SU0634-${LOCAL_DATE_YMD}/HKT`),
);
const details = page.locator('[data-testid="schedule-leg-details"]');
await expect(details).toBeVisible({ timeout: 15000 });