strapi/tests/e2e/utils/shared.ts

174 lines
6.1 KiB
TypeScript
Raw Normal View History

import { test, expect, type Page, type Locator } from '@playwright/test';
import { waitForRestart } from './restart';
import pluralize from 'pluralize';
import { kebabCase } from 'lodash/fp';
type NavItem = string | [string, string] | Locator;
/**
* Execute a test suite only if the condition is true
*/
export const describeOnCondition = (shouldDescribe: boolean) =>
shouldDescribe ? test.describe : test.describe.skip;
/**
* Find an element in the dom after the previous element
* Useful for narrowing down which link to click when there are multiple with the same name
*/
// TODO: instead of siblingText + linkText, accept an array of any number items
export const locateFirstAfter = async (page: Page, firstText: string, secondText: string) => {
// It first searches for text containing "firstText" then uses xpath `following` to find "secondText" after it.
// `translate` is used to make the search case-insensitive
const item = page
.locator(
`xpath=//text()[contains(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), "${firstText.toLowerCase()}")]/following::a[starts-with(translate(., 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), "${secondText.toLowerCase()}")]`
)
.first();
return item;
};
/**
* Navigate to a page and confirm the header, awaiting each step
*/
export const navToHeader = async (page: Page, navItems: NavItem[], headerText: string) => {
for (const navItem of navItems) {
// This handles some common issues
// 1. Uses name^= to only ensure starts with, because for example badge notifications cause "Settings" to really be "Settings 1"
// 2. To avoid duplicates, we accept a locator
// 3. To avoid duplicates and writing complex locators, we accept an array to pass to locateFirstAfter, which matches item0 then finds the next item1 in the dom
let item;
if (typeof navItem === 'string') {
item = page.locator(`role=link[name^="${navItem}"]`).last();
} else if (Array.isArray(navItem)) {
item = await locateFirstAfter(page, navItem[0], navItem[1]);
} else {
// it's a Locator
item = navItem;
}
await expect(item).toBeVisible();
await item.click();
}
// Verify header is correct
const header = page.getByRole('heading', { name: headerText, exact: true });
await expect(header).toBeVisible();
return header;
};
/**
* Skip the tour if the modal is visible
*/
export const skipCtbTour = async (page: Page) => {
const modalSelector = 'role=button[name="Skip the tour"]';
try {
await page.waitForSelector(modalSelector, { timeout: 1000 });
const modal = page.locator(modalSelector);
if (await modal.isVisible()) {
await modal.click();
await expect(modal).not.toBeVisible();
}
} catch (e) {
// The modal did not appear, continue with the test
}
};
/**
* Look for an element containing text, and then click a sibling close button
*/
export const findAndClose = async (
page: Page,
text: string,
role: string = 'status',
closeLabel: string = 'Close'
) => {
// Verify the popup text is visible.
const elements = page.locator(`:has-text("${text}")[role="${role}"]`);
await expect(elements.first()).toBeVisible(); // expect at least one element
// Find all 'Close' buttons that are siblings of the elements containing the specified text.
const closeBtns = page.locator(
`:has-text("${text}")[role="${role}"] ~ button:has-text("${closeLabel}")`
);
// Click all 'Close' buttons.
const count = await closeBtns.count();
for (let i = 0; i < count; i++) {
await closeBtns.nth(i).click();
}
};
export const createSingleType = async (page, data) => {
const { name, singularId, pluralId } = data;
await page.getByRole('button', { name: 'Create new single type' }).click();
await expect(page.getByRole('heading', { name: 'Create a single type' })).toBeVisible();
const displayName = page.getByLabel('Display name');
await displayName.fill(name);
const singularIdField = page.getByLabel('API ID (Singular)');
await expect(singularIdField).toHaveValue(singularId || kebabCase(name));
if (singularId) {
singularIdField.fill(singularId);
}
const pluralIdField = page.getByLabel('API ID (Plural)');
await expect(pluralIdField).toHaveValue(pluralId || pluralize(kebabCase(name)));
if (pluralId) {
pluralIdField.fill(pluralId);
}
await page.getByRole('button', { name: 'Continue' }).click();
// Create an initial text field for it
await expect(page.getByText('Select a field for your single type')).toBeVisible();
await page.getByText('Small or long text').click();
await page.getByLabel('Name', { exact: true }).fill('myattribute');
await page.getByRole('button', { name: 'Finish' }).click();
await page.getByRole('button', { name: 'Save' }).click();
await waitForRestart(page);
await expect(page.getByRole('heading', { name })).toBeVisible();
};
export const createCollectionType = async (page, data) => {
const { name, singularId, pluralId } = data;
await page.getByRole('button', { name: 'Create new collection type' }).click();
await expect(page.getByRole('heading', { name: 'Create a collection type' })).toBeVisible();
const displayName = page.getByLabel('Display name');
await displayName.fill(name);
const singularIdField = page.getByLabel('API ID (Singular)');
await expect(singularIdField).toHaveValue(singularId || kebabCase(name));
if (singularId) {
singularIdField.fill(singularId);
}
const pluralIdField = page.getByLabel('API ID (Plural)');
await expect(pluralIdField).toHaveValue(pluralId || pluralize(kebabCase(name)));
if (pluralId) {
pluralIdField.fill(pluralId);
}
await page.getByRole('button', { name: 'Continue' }).click();
// Create an initial text field for it
await expect(page.getByText('Select a field for your collection type')).toBeVisible();
await page.getByText('Small or long text').click();
await page.getByLabel('Name', { exact: true }).fill('myattribute');
await page.getByRole('button', { name: 'Finish' }).click();
await page.getByRole('button', { name: 'Save' }).click();
await waitForRestart(page);
await expect(page.getByRole('heading', { name })).toBeVisible();
};