test: add smoke test for settings menu (#19585)

Co-authored-by: RufusLeTerrible <MarionLemaire@users.noreply.github.com>
Co-authored-by: Ben Irvin <ben@innerdvations.com>
Co-authored-by: Ben Irvin <ben.irvin@strapi.io>
This commit is contained in:
RufusLeTerrible 2024-08-13 11:08:38 +02:00 committed by GitHub
parent ed2f056a39
commit 6e80fa164c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 102 additions and 6 deletions

View File

@ -0,0 +1,66 @@
import { test } from '@playwright/test';
import { login } from '../../utils/login';
import { describeOnCondition, navToHeader } from '../../utils/shared';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE';
test.describe('Settings', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});
test('every expected feature is displayed', async ({ page }) => {
await navToHeader(page, ['Settings'], 'Overview');
await navToHeader(page, ['Settings', 'API Tokens'], 'API Tokens');
await navToHeader(page, ['Settings', 'Documentation'], 'Documentation');
await navToHeader(page, ['Settings', 'Internationalization'], 'Internationalization');
await navToHeader(page, ['Settings', 'Media Library'], 'Media Library');
await navToHeader(page, ['Settings', 'Single Sign-On'], 'Single Sign-On');
await navToHeader(page, ['Settings', 'Transfer Tokens'], 'Transfer Tokens');
await navToHeader(page, ['Settings', 'Webhooks'], 'Webhooks');
// admin
await navToHeader(page, ['Settings', ['Administration Panel', 'Roles']], 'Roles');
await navToHeader(page, ['Settings', ['Administration Panel', 'Users']], 'Users');
// u&p
await navToHeader(page, ['Settings', ['Users & Permissions', 'Roles']], 'Roles');
await navToHeader(page, ['Settings', ['Users & Permissions', 'Providers']], 'Providers');
await navToHeader(
page,
['Settings', ['Users & Permissions', 'Email templates']],
'Email templates'
);
await navToHeader(
page,
['Settings', ['Users & Permissions', 'Advanced settings']],
'Advanced settings'
);
// EE features should still be displayed because they will show a "purchase" page
await navToHeader(page, ['Settings', 'Review Workflows'], 'Review Workflows');
await navToHeader(page, ['Settings', ['Administration Panel', 'Audit Logs']], 'Audit Logs');
});
describeOnCondition(edition === 'EE')(() => {
test('every EE feature is displayed', async ({ page }) => {
await navToHeader(page, ['Settings', 'Review Workflows'], 'Review Workflows');
await navToHeader(page, ['Settings', ['Administration Panel', 'Audit Logs']], 'Audit Logs');
});
});
});

View File

@ -1,27 +1,57 @@
import { test, Page, expect } from '@playwright/test';
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: string[], headerText: string) => {
export const navToHeader = async (page: Page, navItems: NavItem[], headerText: string) => {
for (const navItem of navItems) {
// This does not use getByRole because sometimes "Settings" is "Settings 1" if there's a badge notification
// BUT if we don't match exact it conflicts with "Advanceed Settings"
// As a workaround, we implement our own startsWith with page.locator
const item = page.locator(`role=link[name^="${navItem}"]`);
// 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;