# What this PR does - Run e2e tests using ops-devenv against environment that includes OnCall & Labels - Add first e2e test for Labels (creating new label key and value) ## Which issue(s) this PR closes Closes https://github.com/grafana/oncall/issues/4083 <!-- *Note*: if you have more than one GitHub issue that this PR closes, be sure to preface each issue link with a [closing keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue). This ensures that the issue(s) are auto-closed once the PR has been merged. --> ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
128 lines
4.5 KiB
TypeScript
128 lines
4.5 KiB
TypeScript
import type { Locator, Page } from '@playwright/test';
|
|
|
|
import { randomInt, randomUUID } from 'crypto';
|
|
|
|
type SelectorType = 'gSelect' | 'grafanaSelect';
|
|
type SelectDropdownValueArgs = {
|
|
page: Page;
|
|
value: string;
|
|
// if set, search for a dropdown that contains this text as its placeholder
|
|
placeholderText?: string;
|
|
// specifies which type of select dropdown we are dealing with (since we currently mix-and-match 3 different components...)
|
|
selectType?: SelectorType;
|
|
// if provided, use this Locator as the root of our search for the dropdown
|
|
startingLocator?: Locator;
|
|
// if true, when selecting the dropdown option, use an exact match, otherwise use a substring contains match
|
|
optionExactMatch?: boolean;
|
|
|
|
// if true, will press enter in the select dropdown. Some dropdowns don't show a list of options
|
|
// and instead the user must press enter to trigger the search
|
|
pressEnterInsteadOfSelectingOption?: boolean;
|
|
};
|
|
|
|
type ClickButtonArgs = {
|
|
page: Page;
|
|
buttonText: string | RegExp;
|
|
// if provided, use this Locator as the root of our search for the button
|
|
startingLocator?: Locator;
|
|
};
|
|
|
|
export const fillInInput = (page: Page, selector: string, value: string) => page.fill(selector, value);
|
|
|
|
export const fillInInputByPlaceholderValue = (page: Page, placeholderValue: string, value: string) =>
|
|
fillInInput(page, `input[placeholder*="${placeholderValue}"]`, value);
|
|
|
|
export const getInputByName = (page: Page, name: string): Locator => page.locator(`input[name="${name}"]`);
|
|
|
|
export const clickButton = async ({ page, buttonText, startingLocator }: ClickButtonArgs): Promise<void> => {
|
|
const baseLocator = startingLocator || page;
|
|
await baseLocator.getByRole('button', { name: buttonText, disabled: false }).click();
|
|
};
|
|
|
|
/**
|
|
* at a minimum must specify selectType OR placeholderText
|
|
* if both are specified selectType takes precedence
|
|
*/
|
|
const openSelect = async ({
|
|
page,
|
|
placeholderText,
|
|
selectType,
|
|
startingLocator,
|
|
}: SelectDropdownValueArgs): Promise<Locator> => {
|
|
/**
|
|
* we currently mix three different dropdown components in the UI..
|
|
* so we need to support all of them :(
|
|
*/
|
|
const dropdownSelectors: Record<SelectorType, string> = {
|
|
gSelect: 'div[class*="GSelect"]',
|
|
grafanaSelect: `div[class*="grafana-select-value-container"] ${
|
|
placeholderText ? `>> text=${placeholderText} ` : ''
|
|
}>> ..`,
|
|
};
|
|
|
|
const dropdownSelector = dropdownSelectors[selectType];
|
|
const placeholderSelector = `text=${placeholderText}`;
|
|
const selector = dropdownSelector || placeholderSelector;
|
|
|
|
const selectElement: Locator = (startingLocator || page).locator(selector);
|
|
await selectElement.waitFor({ state: 'visible' });
|
|
await selectElement.click();
|
|
|
|
return selectElement;
|
|
};
|
|
|
|
/**
|
|
* notice the difference in double quotes - https://playwright.dev/docs/selectors#text-selector
|
|
*/
|
|
const textMatchSelector = (optionExactMatch: boolean, value: string): string =>
|
|
optionExactMatch ? `text="${value}"` : `text=${value}`;
|
|
|
|
const chooseDropdownValue = async ({ page, value, optionExactMatch = true }: SelectDropdownValueArgs): Promise<void> =>
|
|
page.locator(`div[id^="react-select-"][id$="-listbox"] >> ${textMatchSelector(optionExactMatch, value)}`).click();
|
|
|
|
export const selectDropdownValue = async (args: SelectDropdownValueArgs): Promise<Locator> => {
|
|
const { page, value, pressEnterInsteadOfSelectingOption } = args;
|
|
|
|
const selectElement = await openSelect(args);
|
|
await selectElement.pressSequentially(value);
|
|
|
|
if (pressEnterInsteadOfSelectingOption) {
|
|
await page.keyboard.press('Enter');
|
|
} else {
|
|
await chooseDropdownValue(args);
|
|
}
|
|
|
|
return selectElement;
|
|
};
|
|
|
|
export const generateRandomValue = (): string => randomUUID();
|
|
|
|
export const generateRandomValidLabel = (length = 10) => {
|
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
|
let result = '';
|
|
for (let i = 0; i < length; i++) {
|
|
const randomIndex = randomInt(0, characters.length);
|
|
result += characters[randomIndex];
|
|
}
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* wait for the options to appear
|
|
*
|
|
* note that they are not rendered next to the button in the HTML output
|
|
* they're rendered closer to the <body> tag
|
|
*/
|
|
export const selectValuePickerValue = async (
|
|
page: Page,
|
|
valuePickerText: string,
|
|
optionExactMatch = true
|
|
): Promise<void> =>
|
|
(
|
|
await page.waitForSelector(
|
|
`div[class*="grafana-select-menu"] >> ${textMatchSelector(optionExactMatch, valuePickerText)}`
|
|
)
|
|
).click();
|
|
|
|
export const openDropdown = async ({ page, text }: { page: Page; text: string | RegExp }) =>
|
|
page.locator('div').filter({ hasText: text }).nth(1).click();
|