import { type Locator, type Page } from '@playwright/test' import cssEscape from 'css.escape' // ============== // === Filter === // ============== class Filter { constructor(public selector = '') {} visible(this: T): Omit { return new Filter(this.selector + ':visible') as any } first(this: T): Omit { return new Filter(this.selector + ':first') as any } last(this: T): Omit { return new Filter(this.selector + ':last') as any } id(this: T, id: string): Omit { return new Filter(this.selector + '#' + cssEscape(id)) as any } class(...classes: string[]) { return new Filter(this.selector + '.' + classes.map(cssEscape).join('.')) } toString() { return this.selector } } // ================ // === Locators === // ================ // === Button locators === function or(a: (page: Locator | Page) => Locator, b: (page: Locator | Page) => Locator) { return (page: Locator | Page) => a(page).or(b(page)) } export function playOrOpenProjectButton(page: Locator | Page) { return page.getByAltText('Open in editor') } // === Auto-evaluation === export function enableAutoEvaluationButton(page: Locator | Page) { return page.getByAltText('Enable auto-evaluation') } export function disableAutoEvaluationButton(page: Locator | Page) { return page.getByAltText('Disable auto-evaluation') } export const toggleAutoEvaluationButton = or( enableAutoEvaluationButton, disableAutoEvaluationButton, ) // === Documentation === export function showDocumentationButton(page: Locator | Page) { return page.getByAltText('Show documentation') } export function hideDocumentationButton(page: Locator | Page) { return page.getByAltText('Hide documentation') } export const toggleDocumentationButton = or(showDocumentationButton, hideDocumentationButton) // === Visualization === export function showVisualizationButton(page: Locator | Page) { return page.getByAltText('Show visualization') } export function hideVisualizationButton(page: Locator | Page) { return page.getByAltText('Hide visualization') } export const toggleVisualizationButton = or(showVisualizationButton, hideVisualizationButton) // === Visualization selector === export function showVisualizationSelectorButton(page: Locator | Page) { return page.getByAltText('Show visualization selector') } export function hideVisualizationSelectorButton(page: Locator | Page) { return page.getByAltText('Hide visualization selector') } export const toggleVisualizationSelectorButton = or( showVisualizationSelectorButton, hideVisualizationSelectorButton, ) // === Fullscreen === export function enterFullscreenButton(page: Locator | Page) { return page.getByAltText('Enter fullscreen') } export function exitFullscreenButton(page: Locator | Page) { return page.getByAltText('Exit fullscreen') } export const toggleFullscreenButton = or(enterFullscreenButton, exitFullscreenButton) // === Data locators === type SanitizeClassName = T extends `${infer A}.${infer B}` ? SanitizeClassName<`${A}${B}`> : T extends `${infer A} ${infer B}` ? SanitizeClassName<`${A}${B}`> : T function componentLocator(className: SanitizeClassName) { return (page: Locator | Page, filter?: (f: Filter) => { selector: string }) => { return page.locator(`.${className}${filter?.(new Filter()) ?? ''}`) } } export const graphEditor = componentLocator('GraphEditor') export const graphNode = componentLocator('GraphNode') export function graphNodeByBinding(page: Locator | Page, binding: string) { return graphNode(page).filter({ has: page.locator('.binding').and(page.getByText(binding)) }) } // @ts-expect-error export const anyVisualization = componentLocator('GraphVisualization > *') export const circularMenu = componentLocator('CircularMenu') export const addNewNodeButton = componentLocator('PlusButton') export const componentBrowser = componentLocator('ComponentBrowser') export function componentBrowserEntry( page: Locator | Page, filter?: (f: Filter) => { selector: string }, ) { return page.locator( `.ComponentBrowser .list-variant:not(.selected) .component${filter?.(new Filter()) ?? ''}`, ) } export function componentBrowserSelectedEntry( page: Locator | Page, filter?: (f: Filter) => { selector: string }, ) { return page.locator( `.ComponentBrowser .list-variant.selected .component${filter?.(new Filter()) ?? ''}`, ) } export function componentBrowserEntryByLabel(page: Locator | Page, label: string) { return componentBrowserEntry(page).filter({ has: page.getByText(label) }) } export const componentBrowserInput = componentLocator('CBInput') export const jsonVisualization = componentLocator('JSONVisualization') export const tableVisualization = componentLocator('TableVisualization') export const scatterplotVisualization = componentLocator('ScatterplotVisualization') export const histogramVisualization = componentLocator('HistogramVisualization') export const heatmapVisualization = componentLocator('HeatmapVisualization') export const sqlVisualization = componentLocator('SqlVisualization') export const geoMapVisualization = componentLocator('GeoMapVisualization') export const imageBase64Visualization = componentLocator('ImageBase64Visualization') export const warningsVisualization = componentLocator('WarningsVisualization')