From 6e80fa164c140585f2e8ef39ed19449627bf73df Mon Sep 17 00:00:00 2001 From: RufusLeTerrible <97893822+MarionLemaire@users.noreply.github.com> Date: Tue, 13 Aug 2024 11:08:38 +0200 Subject: [PATCH] test: add smoke test for settings menu (#19585) Co-authored-by: RufusLeTerrible Co-authored-by: Ben Irvin Co-authored-by: Ben Irvin --- tests/e2e/tests/settings/smoke-test.spec.ts | 66 +++++++++++++++++++++ tests/e2e/utils/shared.ts | 42 +++++++++++-- 2 files changed, 102 insertions(+), 6 deletions(-) create mode 100644 tests/e2e/tests/settings/smoke-test.spec.ts diff --git a/tests/e2e/tests/settings/smoke-test.spec.ts b/tests/e2e/tests/settings/smoke-test.spec.ts new file mode 100644 index 0000000000..01dcaa5606 --- /dev/null +++ b/tests/e2e/tests/settings/smoke-test.spec.ts @@ -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'); + }); + }); +}); diff --git a/tests/e2e/utils/shared.ts b/tests/e2e/utils/shared.ts index 352bd0fed0..59d72b21d6 100644 --- a/tests/e2e/utils/shared.ts +++ b/tests/e2e/utils/shared.ts @@ -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;