From cc8da034c092950affbb7417ba6b920249f5dae9 Mon Sep 17 00:00:00 2001 From: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com> Date: Tue, 30 Dec 2025 19:15:01 +0530 Subject: [PATCH] test: flaky e2e tests (#26308) * fix another * fix flakes * Update apps/web/playwright/fixtures/apps.ts Co-authored-by: Keith Williams * fix * fix * test fix * fix test * tweak --------- Co-authored-by: Keith Williams --- apps/web/playwright/booking-pages.e2e.ts | 282 +++++++++++++----- apps/web/playwright/fixtures/apps.ts | 4 +- .../web/playwright/managed-event-types.e2e.ts | 2 +- apps/web/playwright/payment-apps.e2e.ts | 2 +- 4 files changed, 218 insertions(+), 72 deletions(-) diff --git a/apps/web/playwright/booking-pages.e2e.ts b/apps/web/playwright/booking-pages.e2e.ts index c0f598bd84..f38e672c50 100644 --- a/apps/web/playwright/booking-pages.e2e.ts +++ b/apps/web/playwright/booking-pages.e2e.ts @@ -1,12 +1,3 @@ -import { expect } from "@playwright/test"; -import { JSDOM } from "jsdom"; - -import { WEBAPP_URL } from "@calcom/lib/constants"; -import { generateHashedLink } from "@calcom/lib/generateHashedLink"; -import { randomString } from "@calcom/lib/random"; -import { SchedulingType } from "@calcom/prisma/enums"; -import type { Schedule, TimeRange } from "@calcom/types/schedule"; - import { test, todo } from "./lib/fixtures"; import { bookFirstEvent, @@ -20,6 +11,13 @@ import { testName, cancelBookingFromBookingsList, } from "./lib/testUtils"; +import { WEBAPP_URL } from "@calcom/lib/constants"; +import { generateHashedLink } from "@calcom/lib/generateHashedLink"; +import { randomString } from "@calcom/lib/random"; +import { SchedulingType } from "@calcom/prisma/enums"; +import type { Schedule, TimeRange } from "@calcom/types/schedule"; +import { expect } from "@playwright/test"; +import { JSDOM } from "jsdom"; const freeUserObj = { name: `Free-user-${randomString(3)}` }; test.describe.configure({ mode: "parallel" }); @@ -33,7 +31,9 @@ test("check SSR and OG - User Event Type", async ({ page, users }) => { name, }); const responsePromise = page.waitForResponse( - (response) => response.url().includes(`/${user.username}/30-min`) && response.status() === 200 + (response) => + response.url().includes(`/${user.username}/30-min`) && + response.status() === 200 ); await page.goto(`/${user.username}/30-min`); await page.content(); @@ -42,17 +42,27 @@ test("check SSR and OG - User Event Type", async ({ page, users }) => { const document = new JSDOM(ssrResponse).window.document; const titleText = document.querySelector("title")?.textContent; - const ogImage = document.querySelector('meta[property="og:image"]')?.getAttribute("content"); - const ogUrl = document.querySelector('meta[property="og:url"]')?.getAttribute("content"); - const canonicalLink = document.querySelector('link[rel="canonical"]')?.getAttribute("href"); + const ogImage = document + .querySelector('meta[property="og:image"]') + ?.getAttribute("content"); + const ogUrl = document + .querySelector('meta[property="og:url"]') + ?.getAttribute("content"); + const canonicalLink = document + .querySelector('link[rel="canonical"]') + ?.getAttribute("href"); expect(titleText).toContain(name); expect(ogUrl).toEqual(`${WEBAPP_URL}/${user.username}/30-min`); await page.waitForSelector('[data-testid="avatar-href"]'); - const avatarLocators = await page.locator('[data-testid="avatar-href"]').all(); + const avatarLocators = await page + .locator('[data-testid="avatar-href"]') + .all(); expect(avatarLocators.length).toBe(1); for (const avatarLocator of avatarLocators) { - expect(await avatarLocator.getAttribute("href")).toEqual(`${WEBAPP_URL}/${user.username}?redirect=false`); + expect(await avatarLocator.getAttribute("href")).toEqual( + `${WEBAPP_URL}/${user.username}?redirect=false` + ); } expect(canonicalLink).toEqual(`${WEBAPP_URL}/${user.username}/30-min`); @@ -157,7 +167,10 @@ test.describe("pro user", () => { await page.goto("/bookings/upcoming"); await page.waitForSelector('[data-testid="bookings"]'); // Click the ellipsis menu button to open the dropdown - await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click(); + await page + .locator('[data-testid="booking-actions-dropdown"]') + .nth(0) + .click(); await page.locator('[data-testid="reschedule"]').click(); await page.waitForURL((url) => { const bookingId = url.searchParams.get("rescheduleUid"); @@ -178,17 +191,31 @@ test.describe("pro user", () => { }) => { const [pro] = users.get(); const [eventType] = pro.eventTypes; - const bookingFixture = await bookings.create(pro.id, pro.username, eventType.id); + const bookingFixture = await bookings.create( + pro.id, + pro.username, + eventType.id + ); // open the wrong eventType (rescheduleUid created for /30min event) - await page.goto(`${pro.username}/${pro.eventTypes[1].slug}?rescheduleUid=${bookingFixture.uid}`); + await page.goto( + `${pro.username}/${pro.eventTypes[1].slug}?rescheduleUid=${bookingFixture.uid}` + ); - await expect(page).toHaveURL(new RegExp(`${pro.username}/${eventType.slug}`)); + await expect(page).toHaveURL( + new RegExp(`${pro.username}/${eventType.slug}`) + ); }); - test("it returns a 404 when a requested event type does not exist", async ({ page, users }) => { + test("it returns a 404 when a requested event type does not exist", async ({ + page, + users, + }) => { const [pro] = users.get(); - const unexistingPageUrl = new URL(`${pro.username}/invalid-event-type`, WEBAPP_URL); + const unexistingPageUrl = new URL( + `${pro.username}/invalid-event-type`, + WEBAPP_URL + ); const response = await page.goto(unexistingPageUrl.href); expect(response?.status()).toBe(404); }); @@ -200,8 +227,12 @@ test.describe("pro user", () => { // Because it tests the entire booking flow + the cancellation + rebooking test.setTimeout(testInfo.timeout * 3); await bookFirstEvent(page); - await expect(page.locator(`[data-testid="attendee-email-${testEmail}"]`)).toHaveText(testEmail); - await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName); + await expect( + page.locator(`[data-testid="attendee-email-${testEmail}"]`) + ).toHaveText(testEmail); + await expect( + page.locator(`[data-testid="attendee-name-${testName}"]`) + ).toHaveText(testName); const [pro] = users.get(); await pro.apiLogin(); @@ -224,8 +255,12 @@ test.describe("pro user", () => { // Because it tests the entire booking flow + the cancellation + rebooking test.setTimeout(testInfo.timeout * 3); await bookFirstEvent(page); - await expect(page.locator(`[data-testid="attendee-email-${testEmail}"]`)).toHaveText(testEmail); - await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName); + await expect( + page.locator(`[data-testid="attendee-email-${testEmail}"]`) + ).toHaveText(testEmail); + await expect( + page.locator(`[data-testid="attendee-name-${testName}"]`) + ).toHaveText(testName); const [pro] = users.get(); await pro.apiLogin(); @@ -242,7 +277,9 @@ test.describe("pro user", () => { await page.goto(`/reschedule/${bookingUid}`); expect(page.url()).not.toContain("rescheduleUid"); - const cancelledHeadline = page.locator('[data-testid="cancelled-headline"]'); + const cancelledHeadline = page.locator( + '[data-testid="cancelled-headline"]' + ); await expect(cancelledHeadline).toBeVisible(); }); @@ -257,14 +294,18 @@ test.describe("pro user", () => { await page.goto("/bookings/unconfirmed"); await Promise.all([ page.click('[data-testid="confirm"]'), - page.waitForResponse((response) => response.url().includes("/api/trpc/bookings/confirm")), + page.waitForResponse((response) => + response.url().includes("/api/trpc/bookings/confirm") + ), ]); // This is the only booking in there that needed confirmation and now it should be empty screen await expect(page.locator('[data-testid="empty-screen"]')).toBeVisible(); }); test("can book an unconfirmed event multiple times", async ({ page }) => { - await page.locator('[data-testid="event-type-link"]:has-text("Opt in")').click(); + await page + .locator('[data-testid="event-type-link"]:has-text("Opt in")') + .click(); await selectFirstAvailableTimeSlotNextMonth(page); const pageUrl = page.url(); @@ -281,7 +322,9 @@ test.describe("pro user", () => { test("booking an unconfirmed event with the same email brings you to the original request", async ({ page, }) => { - await page.locator('[data-testid="event-type-link"]:has-text("Opt in")').click(); + await page + .locator('[data-testid="event-type-link"]:has-text("Opt in")') + .click(); await selectFirstAvailableTimeSlotNextMonth(page); const pageUrl = page.url(); @@ -312,12 +355,17 @@ test.describe("pro user", () => { await expect(page.locator("[data-testid=success-page]")).toBeVisible(); const promises = additionalGuests.map(async (email) => { - await expect(page.locator(`[data-testid="attendee-email-${email}"]`)).toHaveText(email); + await expect( + page.locator(`[data-testid="attendee-email-${email}"]`) + ).toHaveText(email); }); await Promise.all(promises); }); - test("Time slots should be reserved when selected", async ({ page, browser }) => { + test("Time slots should be reserved when selected", async ({ + page, + browser, + }) => { const initialUrl = page.url(); await page.locator('[data-testid="event-type-link"]').first().click(); await selectFirstAvailableTimeSlotNextMonth(page); @@ -325,16 +373,29 @@ test.describe("pro user", () => { const pageTwoInNewContext = await newContext.newPage(); await pageTwoInNewContext.goto(initialUrl); await pageTwoInNewContext.waitForURL(initialUrl); - await pageTwoInNewContext.locator('[data-testid="event-type-link"]').first().click(); + await pageTwoInNewContext + .locator('[data-testid="event-type-link"]') + .first() + .click(); - await pageTwoInNewContext.locator('[data-testid="incrementMonth"]').waitFor(); + await pageTwoInNewContext + .locator('[data-testid="incrementMonth"]') + .waitFor(); await pageTwoInNewContext.click('[data-testid="incrementMonth"]'); - await pageTwoInNewContext.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor(); - await pageTwoInNewContext.locator('[data-testid="day"][data-disabled="false"]').nth(0).click(); + await pageTwoInNewContext + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .waitFor(); + await pageTwoInNewContext + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .click(); // 9:30 should be the first available time slot await pageTwoInNewContext.locator('[data-testid="time"]').nth(0).waitFor(); - const firstSlotAvailable = pageTwoInNewContext.locator('[data-testid="time"]').nth(0); + const firstSlotAvailable = pageTwoInNewContext + .locator('[data-testid="time"]') + .nth(0); // Find text inside the element const firstSlotAvailableText = await firstSlotAvailable.innerText(); expect(firstSlotAvailableText).toContain("9:30"); @@ -346,7 +407,9 @@ test.describe("pro user", () => { }) => { const initialUrl = page.url(); await page.waitForSelector('[data-testid="event-type-link"]'); - const eventTypeLink = page.locator('[data-testid="event-type-link"]').first(); + const eventTypeLink = page + .locator('[data-testid="event-type-link"]') + .first(); await eventTypeLink.click(); await selectFirstAvailableTimeSlotNextMonth(page); @@ -355,7 +418,9 @@ test.describe("pro user", () => { await pageTwo.waitForURL(initialUrl); await pageTwo.waitForSelector('[data-testid="event-type-link"]'); - const eventTypeLinkTwo = pageTwo.locator('[data-testid="event-type-link"]').first(); + const eventTypeLinkTwo = pageTwo + .locator('[data-testid="event-type-link"]') + .first(); await eventTypeLinkTwo.waitFor(); await eventTypeLinkTwo.click(); @@ -364,8 +429,14 @@ test.describe("pro user", () => { await pageTwo.locator('[data-testid="incrementMonth"]').waitFor(); await pageTwo.click('[data-testid="incrementMonth"]'); - await pageTwo.locator('[data-testid="day"][data-disabled="false"]').nth(0).waitFor(); - await pageTwo.locator('[data-testid="day"][data-disabled="false"]').nth(0).click(); + await pageTwo + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .waitFor(); + await pageTwo + .locator('[data-testid="day"][data-disabled="false"]') + .nth(0) + .click(); await pageTwo.locator('[data-testid="time"]').nth(0).waitFor(); const firstSlotAvailable = pageTwo.locator('[data-testid="time"]').nth(0); @@ -384,7 +455,9 @@ test.describe("prefill", () => { await test.step("from session", async () => { await selectFirstAvailableTimeSlotNextMonth(page); - await expect(page.locator('[name="name"]')).toHaveValue(prefill.name || ""); + await expect(page.locator('[name="name"]')).toHaveValue( + prefill.name || "" + ); await expect(page.locator('[name="email"]')).toHaveValue(prefill.email); }); @@ -399,7 +472,9 @@ test.describe("prefill", () => { }); }); - test("Persist the field values when going back and coming back to the booking form", async ({ page }) => { + test("Persist the field values when going back and coming back to the booking form", async ({ + page, + }) => { await page.goto("/pro/30min"); await selectFirstAvailableTimeSlotNextMonth(page); await page.fill('[name="name"]', "John Doe"); @@ -409,7 +484,9 @@ test.describe("prefill", () => { await selectFirstAvailableTimeSlotNextMonth(page); await expect(page.locator('[name="name"]')).toHaveValue("John Doe"); - await expect(page.locator('[name="email"]')).toHaveValue("john@example.com"); + await expect(page.locator('[name="email"]')).toHaveValue( + "john@example.com" + ); await expect(page.locator('[name="notes"]')).toHaveValue("Test notes"); }); @@ -429,7 +506,9 @@ test.describe("prefill", () => { }); }); - test("skip confirm step if all fields are prefilled from query params", async ({ page }) => { + test("skip confirm step if all fields are prefilled from query params", async ({ + page, + }) => { await page.goto("/pro/30min"); const url = new URL(page.url()); url.searchParams.set("name", testName); @@ -440,7 +519,9 @@ test.describe("prefill", () => { await page.goto(url.toString()); await selectFirstAvailableTimeSlotNextMonth(page); - await expect(page.locator('[data-testid="skip-confirm-book-button"]')).toBeVisible(); + await expect( + page.locator('[data-testid="skip-confirm-book-button"]') + ).toBeVisible(); await page.click('[data-testid="skip-confirm-book-button"]'); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); @@ -455,7 +536,15 @@ test.describe("Booking on different layouts", () => { start: new Date(new Date().setUTCHours(9, 0, 0, 0)), end: new Date(new Date().setUTCHours(17, 0, 0, 0)), }; - const schedule: Schedule = [[], [dateRanges], [dateRanges], [dateRanges], [dateRanges], [dateRanges], []]; + const schedule: Schedule = [ + [], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [], + ]; const user = await users.create({ schedule }); await page.goto(`/${user.username}`); @@ -490,6 +579,13 @@ test.describe("Booking on different layouts", () => { await page.click('[data-testid="incrementMonth"]'); + await page.waitForURL((url) => { + return url.searchParams.has("month"); + }) + + await page.reload(); + await page.waitForLoadState("networkidle"); + await page.locator('[data-testid="time"]').nth(1).click(); // Fill what is this meeting about? name email and notes @@ -513,7 +609,15 @@ test.describe("Booking round robin event", () => { end: new Date(new Date().setUTCHours(17, 0, 0, 0)), }; - const schedule: Schedule = [[], [dateRanges], [dateRanges], [dateRanges], [dateRanges], [dateRanges], []]; + const schedule: Schedule = [ + [], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [dateRanges], + [], + ]; const testUser = await users.create( { schedule }, @@ -621,12 +725,16 @@ test.describe("Event type with disabled cancellation and rescheduling", () => { bookingId = pathSegments[pathSegments.length - 1]; }); - test("Reschedule and cancel buttons should be hidden on success page", async ({ page }) => { + test("Reschedule and cancel buttons should be hidden on success page", async ({ + page, + }) => { await expect(page.locator('[data-testid="reschedule-link"]')).toBeHidden(); await expect(page.locator('[data-testid="cancel"]')).toBeHidden(); }); - test("Direct access to reschedule/{bookingId} should redirect to success page", async ({ page }) => { + test("Direct access to reschedule/{bookingId} should redirect to success page", async ({ + page, + }) => { await page.goto(`/reschedule/${bookingId}`); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); @@ -634,15 +742,21 @@ test.describe("Event type with disabled cancellation and rescheduling", () => { await page.waitForURL((url) => url.pathname === `/booking/${bookingId}`); }); - test("Using rescheduleUid query parameter should redirect to success page", async ({ page }) => { - await page.goto(`/${user.username}/no-cancel-no-reschedule?rescheduleUid=${bookingId}`); + test("Using rescheduleUid query parameter should redirect to success page", async ({ + page, + }) => { + await page.goto( + `/${user.username}/no-cancel-no-reschedule?rescheduleUid=${bookingId}` + ); await expect(page.locator("[data-testid=success-page]")).toBeVisible(); await page.waitForURL((url) => url.pathname === `/booking/${bookingId}`); }); - test("Should prevent cancellation and show an error message", async ({ page }) => { + test("Should prevent cancellation and show an error message", async ({ + page, + }) => { const csrfTokenResponse = await page.request.get("/api/csrf"); const { csrfToken } = await csrfTokenResponse.json(); const response = await page.request.post("/api/cancel", { @@ -657,10 +771,15 @@ test.describe("Event type with disabled cancellation and rescheduling", () => { expect(response.status()).toBe(400); const responseBody = await response.json(); - expect(responseBody.message).toBe("This event type does not allow cancellations"); + expect(responseBody.message).toBe( + "This event type does not allow cancellations" + ); }); }); -test("Should throw error when both seatsPerTimeSlot and recurringEvent are set", async ({ page, users }) => { +test("Should throw error when both seatsPerTimeSlot and recurringEvent are set", async ({ + page, + users, +}) => { const user = await users.create({ name: `Test-user-${randomString(4)}`, eventTypes: [ @@ -699,7 +818,11 @@ test.describe("GTM container", () => { await users.create(); }); - test("global GTM should not be loaded on private booking link", async ({ page, users, prisma }) => { + test("global GTM should not be loaded on private booking link", async ({ + page, + users, + prisma, + }) => { const [user] = users.get(); const eventType = await user.getFirstEventAsOwner(); @@ -722,9 +845,12 @@ test.describe("GTM container", () => { }); const getScheduleRespPromise = page.waitForResponse( - (response) => response.url().includes("getSchedule") && response.status() === 200 + (response) => + response.url().includes("getSchedule") && response.status() === 200 + ); + await page.goto( + `/d/${eventWithPrivateLink.hashedLink[0]?.link}/${eventWithPrivateLink.slug}` ); - await page.goto(`/d/${eventWithPrivateLink.hashedLink[0]?.link}/${eventWithPrivateLink.slug}`); await page.waitForLoadState("domcontentloaded"); await getScheduleRespPromise; @@ -732,15 +858,23 @@ test.describe("GTM container", () => { await expect(injectedScript).not.toBeAttached(); }); - test("global GTM should be loaded on non-booking pages", async ({ page, users }) => { - test.skip(!process.env.NEXT_PUBLIC_BODY_SCRIPTS, "Skipping test as NEXT_PUBLIC_BODY_SCRIPTS is not set"); + test("global GTM should be loaded on non-booking pages", async ({ + page, + users, + }) => { + test.skip( + !process.env.NEXT_PUBLIC_BODY_SCRIPTS, + "Skipping test as NEXT_PUBLIC_BODY_SCRIPTS is not set" + ); const [user] = users.get(); await user.apiLogin(); // Go to /insights page and wait for one of the common API call to complete const eventsByStatusRespPromise = page.waitForResponse( - (response) => response.url().includes("getEventTypesFromGroup") && response.status() === 200 + (response) => + response.url().includes("getEventTypesFromGroup") && + response.status() === 200 ); await page.goto(`/insights`); await page.waitForLoadState("domcontentloaded"); @@ -755,7 +889,11 @@ test.describe("GTM container", () => { }); test.describe("Past booking cancellation", () => { - test("Cancel button should be hidden for past bookings", async ({ page, users, bookings }) => { + test("Cancel button should be hidden for past bookings", async ({ + page, + users, + bookings, + }) => { const user = await users.create({ name: "Test User", }); @@ -766,15 +904,23 @@ test.describe("Past booking cancellation", () => { pastDate.setDate(pastDate.getDate() - 1); const endDate = new Date(pastDate.getTime() + 30 * 60 * 1000); - const booking = await bookings.create(user.id, user.username, user.eventTypes[0].id, { - title: "Past Meeting", - startTime: pastDate, - endTime: endDate, - status: "ACCEPTED", - }); + const booking = await bookings.create( + user.id, + user.username, + user.eventTypes[0].id, + { + title: "Past Meeting", + startTime: pastDate, + endTime: endDate, + status: "ACCEPTED", + } + ); await page.goto("/bookings/past"); - await page.locator('[data-testid="booking-actions-dropdown"]').nth(0).click(); + await page + .locator('[data-testid="booking-actions-dropdown"]') + .nth(0) + .click(); await expect(page.locator('[data-testid="cancel"]')).toBeDisabled(); await page.goto(`/booking/${booking.uid}`); diff --git a/apps/web/playwright/fixtures/apps.ts b/apps/web/playwright/fixtures/apps.ts index e2c359b105..e44f64909c 100644 --- a/apps/web/playwright/fixtures/apps.ts +++ b/apps/web/playwright/fixtures/apps.ts @@ -117,7 +117,7 @@ export function createAppsFixture(page: Page) { }, goToAppsTab: async () => { await page.getByTestId("vertical-tab-apps").click(); - await expect(page.getByTestId("vertical-tab-apps")).toHaveAttribute("aria-current", "page"); + await expect(page.getByTestId("vertical-tab-apps").first()).toHaveAttribute("aria-current", "page"); }, activeApp: async (app: string) => { await page.locator(`[data-testid='${app}-app-switch']`).click(); @@ -127,7 +127,7 @@ export function createAppsFixture(page: Page) { }, verifyAppsInfoNew: async (app: string, eventTypeId: number) => { await page.goto(`event-types/${eventTypeId}?tabName=apps`); - await expect(page.getByTestId("vertical-tab-apps")).toHaveAttribute("aria-current", "page"); // fix the race condition + await expect(page.getByTestId("vertical-tab-apps").first()).toHaveAttribute("aria-current", "page"); // fix the race condition await expect(page.locator(`[data-testid='${app}-app-switch'][data-state="checked"]`)).toBeVisible(); }, }; diff --git a/apps/web/playwright/managed-event-types.e2e.ts b/apps/web/playwright/managed-event-types.e2e.ts index a9ee8b8912..2e6898dabd 100644 --- a/apps/web/playwright/managed-event-types.e2e.ts +++ b/apps/web/playwright/managed-event-types.e2e.ts @@ -34,7 +34,7 @@ test.describe("Managed Event Types", () => { // Let's create a team // Going to create an event type await page.goto("/event-types"); - const tabItem = page.getByTestId(`horizontal-tab-Owner`); + const tabItem = page.getByTestId("horizontal-tab-Owner").first(); await expect(tabItem).toBeVisible(); // We wait until loading is finished await page.waitForSelector('[data-testid="event-types"]'); diff --git a/apps/web/playwright/payment-apps.e2e.ts b/apps/web/playwright/payment-apps.e2e.ts index 2b5cf89b1e..866a0a24b2 100644 --- a/apps/web/playwright/payment-apps.e2e.ts +++ b/apps/web/playwright/payment-apps.e2e.ts @@ -11,7 +11,7 @@ test.afterEach(({ users }) => users.deleteAll()); async function goToAppsTab(page: Page, eventTypeId?: number) { await page.goto(`event-types/${eventTypeId}?tabName=apps`); - await expect(page.getByTestId("vertical-tab-apps")).toHaveAttribute("aria-current", "page"); + await expect(page.getByTestId("vertical-tab-apps").first()).toHaveAttribute("aria-current", "page"); } test.describe("Payment app", () => {