Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
mckn committed Nov 15, 2024
1 parent d793529 commit d61d0cd
Show file tree
Hide file tree
Showing 9 changed files with 155 additions and 19 deletions.
4 changes: 4 additions & 0 deletions packages/plugin-e2e/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import { GrafanaPage } from './models/pages/GrafanaPage';
import { VariableEditPage } from './models/pages/VariableEditPage';
import { variablePage } from './fixtures/variablePage';
import { gotoVariablePage } from './fixtures/commands/gotoVariablePage';
import { toHaveOption } from './matchers/toHaveOption';
import { toHaveOptions } from './matchers/toHaveOptions';

// models
export { DataSourcePicker } from './models/components/DataSourcePicker';
Expand Down Expand Up @@ -92,6 +94,8 @@ export const expect = baseExpect.extend({
toHaveAlert,
toDisplayPreviews,
toBeOK,
toHaveOption,
toHaveOptions,
});

export { selectors } from '@playwright/test';
Expand Down
35 changes: 35 additions & 0 deletions packages/plugin-e2e/src/matchers/toHaveOption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { expect, MatcherReturnType } from '@playwright/test';
import { getMessage } from './utils';
import { ContainTextOptions } from '../types';

import { Select } from '../models/components/Select';

export async function toHaveOption(
select: Select,
value: string | RegExp,
options?: ContainTextOptions
): Promise<MatcherReturnType> {
let actual = '';

try {
actual = await select
.locator('div[class*="-grafana-select-value-container"]')
.locator('div[class*="-singleValue"]')
.innerText(options);

expect(actual).toMatch(value);

return {
pass: true,
actual: actual,
expected: value,
message: () => `Value successfully selected`,
};
} catch (err: unknown) {
return {
message: () => getMessage(value.toString(), err instanceof Error ? err.toString() : 'Unknown error'),
pass: false,
actual,
};
}
}
31 changes: 31 additions & 0 deletions packages/plugin-e2e/src/matchers/toHaveOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { expect, MatcherReturnType } from '@playwright/test';
import { getMessage } from './utils';

import { MultiSelect } from '../models/components/MultiSelect';

export async function toHaveOptions(select: MultiSelect, values: Array<string | RegExp>): Promise<MatcherReturnType> {
let actual = '';

try {
const actual = await select
.locator('div[class*="-grafana-select-multi-value-container"]')
.locator('div[class*="-grafana-select-multi-value-container"] > div')
.allInnerTexts();

expect(actual).toMatchObject(values);

return {
pass: true,
actual: actual,
expected: values,
message: () => `Values successfully selected`,
};
} catch (err: unknown) {
return {
message: () => getMessage(values.join(', '), err instanceof Error ? err.toString() : 'Unknown error'),
pass: false,
actual,
expected: values,
};
}
}
11 changes: 11 additions & 0 deletions packages/plugin-e2e/src/models/components/ComponentBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Locator } from '@playwright/test';

type LocatorParams = Parameters<Locator['locator']>;

export abstract class ComponentBase {
constructor(protected readonly element: Locator) {}

locator(selectorOrLocator: LocatorParams[0], options?: LocatorParams[1]): Locator {
return this.element.locator(selectorOrLocator, options);
}
}
22 changes: 22 additions & 0 deletions packages/plugin-e2e/src/models/components/MultiSelect.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Locator } from '@playwright/test';
import { PluginTestCtx } from '../../types';
import { openSelect, selectByValueOrLabel } from './Select';
import { ComponentBase } from './ComponentBase';

type OptionsType = Parameters<Locator['selectOption']>[1];

export class MultiSelect extends ComponentBase {
constructor(private ctx: PluginTestCtx, element: Locator) {
super(element);
}

async selectOptions(values: string[], options?: OptionsType): Promise<string[]> {
const menu = await openSelect(this.element, options);

return Promise.all(
values.map((value) => {
return selectByValueOrLabel(value, menu, options);
})
);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Locator } from '@playwright/test';
import { PluginTestCtx } from '../../types';
import { createSelectProxy } from './SelectProxy';
import { ColorPicker } from './ColorPicker';
import { UnitPicker } from './UnitPicker';
import { Select } from './Select';
import { MultiSelect } from './MultiSelect';

export class PanelEditOptionsGroup {
constructor(private ctx: PluginTestCtx, public readonly element: Locator, private groupLabel: string) {}
Expand All @@ -29,8 +30,12 @@ export class PanelEditOptionsGroup {
return this.getNumberInput(label);
}

getSelect(label: string): Locator {
return createSelectProxy(this.getByLabel(label));
getSelect(label: string): Select {
return new Select(this.ctx, this.getByLabel(label));
}

getMultiSelect(label: string): MultiSelect {
return new MultiSelect(this.ctx, this.getByLabel(label));
}

getColorPicker(label: string): ColorPicker {
Expand Down
43 changes: 43 additions & 0 deletions packages/plugin-e2e/src/models/components/Select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Locator } from '@playwright/test';
import { PluginTestCtx } from '../../types';
import { ComponentBase } from './ComponentBase';

type OptionsType = Parameters<Locator['selectOption']>[1];

export class Select extends ComponentBase {
constructor(private ctx: PluginTestCtx, element: Locator) {
super(element);
}

async selectOption(values: string, options?: OptionsType): Promise<string> {
const menu = await openSelect(this.element, options);
return selectByValueOrLabel(values, menu, options);
}
}

export async function openSelect(select: Locator, options?: OptionsType): Promise<Locator> {
await select.getByRole('combobox').click(options);
return select.page().getByLabel('Select options menu', {
exact: true,
});
}

export async function selectByValueOrLabel(
labelOrValue: string,
menu: Locator,
options?: OptionsType
): Promise<string> {
if (!labelOrValue) {
throw new Error(`Could not select option: "${labelOrValue}"`);
}

const option = menu.getByRole('option', { name: labelOrValue, exact: true });
await option.click(options);
const value = await option.locator('span').textContent(options);

if (!value) {
throw new Error(`Could not select option: "${labelOrValue}"`);
}

return value;
}
1 change: 0 additions & 1 deletion packages/plugin-e2e/src/models/pages/GrafanaPage.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Locator, Request, Response } from '@playwright/test';
import { getByGrafanaSelectorOptions, GrafanaPageArgs, NavigateOptions, PluginTestCtx } from '../../types';
import { getByGrafanaSelector } from '../utils';

/**
* Base class for all Grafana pages.
Expand Down
16 changes: 1 addition & 15 deletions packages/plugin-e2e/src/models/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Locator, Page } from '@playwright/test';
import { getByGrafanaSelectorOptions } from '../types';
import { Page } from '@playwright/test';

export const radioButtonSetChecked = async (
page: Page,
Expand All @@ -13,16 +12,3 @@ export const radioButtonSetChecked = async (
await page.getByText(label, options).setChecked(checked);
}
};

export function getByGrafanaSelector(
locator: Page['locator'],
selector: string,
options?: getByGrafanaSelectorOptions
): Locator {
const startsWith = options?.startsWith ? '^' : '';
if (selector.startsWith('data-testid')) {
return locator(`[data-testid${startsWith}="${selector}"]`);
}

return locator(`[aria-label${startsWith}="${selector}"]`);
}

0 comments on commit d61d0cd

Please sign in to comment.