test(sqllab): migrate Cypress E2E tests to Playwright (#39071)

This commit is contained in:
Joe Li
2026-04-14 10:31:37 -07:00
committed by GitHub
parent 002d8ad1e4
commit 3e25f02da9
25 changed files with 1156 additions and 454 deletions

View File

@@ -21,6 +21,10 @@ import { Locator, Page } from '@playwright/test';
/**
* Tabs component for Ant Design tab navigation.
*
* Expects the locator to point to the `.ant-tabs` wrapper element
* (not the inner tablist) so that `nav` can scope to the outer tab bar
* and exclude nested/inner tabs (e.g. Results / Query history in SQL Lab).
*/
export class Tabs {
readonly page: Page;
@@ -28,23 +32,54 @@ export class Tabs {
constructor(page: Page, locator?: Locator) {
this.page = page;
// Default to the tablist role if no specific locator provided
this.locator = locator ?? page.getByRole('tablist');
this.locator = locator ?? page.locator('.ant-tabs').first();
}
/**
* Gets the tablist element locator
* The root element locator for this tabs component.
*/
get element(): Locator {
return this.locator;
}
/**
* Gets a tab by name, scoped to this tablist's container
* The tab navigation bar for this component.
* Scoped to the first `.ant-tabs-nav` descendant so that queries
* only hit this component's tabs, never nested/inner tab bars.
*/
protected get nav(): Locator {
return this.locator.locator('.ant-tabs-nav').first();
}
/**
* Returns the number of tabs.
* Counts `.ant-tabs-tab` wrappers in the nav bar — one per physical tab,
* regardless of inner role="tab" elements (btn, remove button, etc.).
*/
async getTabCount(): Promise<number> {
return this.nav.locator('.ant-tabs-tab').count();
}
/**
* Returns the text content of all tabs.
*/
async getTabNames(): Promise<string[]> {
return this.nav.locator('.ant-tabs-tab-btn').allTextContents();
}
/**
* Gets a tab button by name, scoped to this component's nav bar.
* Anchored at start (^) with negative lookahead (?!\d) to prevent
* partial matches: "Query" won't match "Query 1", and "Query 1"
* won't match "Query 10". Trailing icon text (e.g. " circle-solid")
* is allowed since (?!\d) permits non-digit suffixes.
* @param tabName - The name/label of the tab
*/
getTab(tabName: string): Locator {
return this.locator.getByRole('tab', { name: tabName });
const escaped = tabName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return this.nav
.locator('.ant-tabs-tab-btn')
.filter({ hasText: new RegExp(`^${escaped}(?!\\d)`) });
}
/**
@@ -63,6 +98,16 @@ export class Tabs {
return this.page.getByRole('tabpanel', { name: tabName });
}
/**
* Returns the name of the currently active tab.
*/
async getActiveTabName(): Promise<string> {
const text = await this.nav
.locator('.ant-tabs-tab-active .ant-tabs-tab-btn')
.textContent();
return text?.trim() ?? '';
}
/**
* Checks if a tab is selected
* @param tabName - The name/label of the tab