Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tests(e2e): improve login util stability in develop #20224

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ jobs:
uses: ./.github/actions/yarn-nm-install

- name: Install Playwright Browsers
run: npx playwright@1.38.1 install --with-deps
run: npx playwright@1.43.1 install --with-deps

- name: Monorepo build
uses: ./.github/actions/run-build
Expand Down Expand Up @@ -216,7 +216,7 @@ jobs:
uses: ./.github/actions/yarn-nm-install

- name: Install Playwright Browsers
run: npx playwright@1.38.1 install --with-deps
run: npx playwright@1.43.1 install --with-deps

- name: Monorepo build
uses: ./.github/actions/run-build
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"@babel/core": "^7.20.12",
"@babel/eslint-parser": "^7.19.1",
"@babel/preset-react": "7.18.6",
"@playwright/test": "1.38.1",
"@playwright/test": "1.43.1",
"@strapi/admin-test-utils": "workspace:*",
"@strapi/eslint-config": "0.2.0",
"@swc/cli": "0.1.62",
Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/constants.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

const { CUSTOM_TRANSFER_TOKEN_ACCESS_KEY } = require('./app-template/template/src/constants');

const ALLOWED_CONTENT_TYPES = [
Expand All @@ -24,9 +26,17 @@ const ALLOWED_CONTENT_TYPES = [
const ADMIN_EMAIL_ADDRESS = 'test@testing.com';
const ADMIN_PASSWORD = 'Testing123!';

const TITLE_LOGIN = 'Strapi Admin';
const TITLE_HOME = 'Homepage';

const URL_ROOT = '/admin';

module.exports = {
ADMIN_EMAIL_ADDRESS,
ADMIN_PASSWORD,
ALLOWED_CONTENT_TYPES,
CUSTOM_TRANSFER_TOKEN_ACCESS_KEY,
TITLE_LOGIN,
TITLE_HOME,
URL_ROOT,
};
68 changes: 68 additions & 0 deletions tests/e2e/tests/admin/api-tokens.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { test, expect, Page } from '@playwright/test';
import { login } from '../../utils/login';
import { navToHeader } from '../../utils/shared';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';

const createAPIToken = async (page, tokenName, duration, type) => {
await navToHeader(page, ['Settings', 'API Tokens', 'Create new API Token'], 'Create API Token');

await page.getByLabel('Name*').click();
await page.getByLabel('Name*').fill(tokenName);

await page.getByLabel('Token duration').click();
await page.getByRole('option', { name: duration }).click();

await page.getByLabel('Token type').click();
await page.getByRole('option', { name: type }).click();

await page.getByRole('button', { name: 'Save' }).click();

await expect(page.getByText('Make sure to copy this token')).toBeVisible();
await expect(page.getByText('Expiration date:')).toBeVisible();
};

test.describe('API Tokens', () => {
let page: Page;

// For some reason, logging in after each token is extremely flaky, so we'll just do it once
test.beforeAll(async ({ browser }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
page = await browser.newPage();
await login({ page });
});

test.afterAll(async ({ browser }) => {
page.close();
});

// Test token creation
const testCases = [
['30-day Read-only token', '30 days', 'Read-only'],
['30-day full-access token', '30 days', 'Full access'],
['7-day token', '7 days', 'Full access'],
['90-day token', '90 days', 'Full access'],
['unlimited token', 'Unlimited', 'Full access'],
];
for (const [name, duration, type] of testCases) {
test(`A user should be able to create a ${name}`, async ({}) => {
await createAPIToken(page, name, duration, type);
});
}

test('Created tokens list page should be correct', async ({}) => {
await createAPIToken(page, 'my test token', 'unlimited', 'Full access');

// if we don't wait until createdAt is at least 1s, we see "NaN" for the timestamp
// TODO: fix the bug and remove this
await page.waitForTimeout(1100);

await navToHeader(page, ['Settings', 'API Tokens'], 'API Tokens');

const nameCell = page.getByRole('gridcell', { name: 'my test token', exact: true });
await expect(nameCell).toBeVisible();

// Locate the parent of nameCell and then search within it for the timestamp
const parentRow = nameCell.locator('xpath=..');
await expect(parentRow).toContainText(/\d+ (second|minute)s? ago/);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { toggleRateLimiting } from '../../utils/rate-limit';
import { ADMIN_EMAIL_ADDRESS, ADMIN_PASSWORD } from '../../constants';
import { ADMIN_EMAIL_ADDRESS, ADMIN_PASSWORD, TITLE_HOME, TITLE_LOGIN } from '../../constants';
import { login } from '../../utils/login';

test.describe('Login', () => {
Expand All @@ -14,26 +14,33 @@ test.describe('Login', () => {
test('A user should be able to log in with or without making their authentication persistent', async ({
page,
context,
browser,
}) => {
// Test without making user authentication persistent
await login({ page });
await expect(page).toHaveTitle('Homepage');
await expect(page).toHaveTitle(TITLE_HOME);

// Close the page and open a new one
await page.close();

page = await context.newPage();
await page.goto('/admin');
await expect(page).toHaveTitle('Strapi Admin');
const nonPersistentPage = await context.newPage();
await Promise.all([
nonPersistentPage.waitForLoadState('networkidle'),
nonPersistentPage.goto('/admin'),
]);
await expect(nonPersistentPage).toHaveTitle(TITLE_LOGIN);

// Test with making user authentication persistent
await login({ page, rememberMe: true });
await expect(page).toHaveTitle('Homepage');

await page.close();

page = await context.newPage();
await page.goto('/admin');
await expect(page).toHaveTitle('Homepage');
await login({ page: nonPersistentPage, rememberMe: true });
await expect(nonPersistentPage).toHaveTitle(TITLE_HOME);

// Close the page and open a new one
await nonPersistentPage.close();
const persistentPage = await context.newPage();
await Promise.all([
persistentPage.waitForLoadState('networkidle'),
persistentPage.goto('/admin'),
]);
await expect(persistentPage).toHaveTitle(TITLE_HOME);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { login } from '../../utils/login';
test.describe('Log Out', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';

import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { ADMIN_EMAIL_ADDRESS, ADMIN_PASSWORD } from '../../constants';
import { ADMIN_EMAIL_ADDRESS, ADMIN_PASSWORD, TITLE_HOME } from '../../constants';

/**
* Fill in the sign up form with valid values
Expand All @@ -26,7 +26,7 @@ export const fillValidSignUpForm = async ({ page }) => {
test.describe('Sign Up', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('without-admin.tar');
await page.goto('/admin');
await page.goto('/admin', { waitUntil: 'networkidle' });
await fillValidSignUpForm({ page });
});

Expand Down Expand Up @@ -94,8 +94,11 @@ test.describe('Sign Up', () => {
test('a user should be able to signup when the strapi instance starts fresh', async ({
page,
}) => {
await page.getByRole('button', { name: "Let's start" }).click();
await Promise.all([
page.waitForLoadState('networkidle'),
page.getByRole('button', { name: "Let's start" }).click(),
]);

await expect(page).toHaveTitle('Homepage');
await expect(page).toHaveTitle(TITLE_HOME);
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test';
import { login } from '../../../utils/login';
import { resetDatabaseAndImportDataFromPath } from '../../../utils/dts-import';
import { navToHeader } from '../../../utils/shared';
import { login } from '../../utils/login';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { navToHeader } from '../../utils/shared';

const createTransferToken = async (page, tokenName, duration, type) => {
await navToHeader(
Expand All @@ -28,7 +28,6 @@ const createTransferToken = async (page, tokenName, duration, type) => {
test.describe('Transfer Tokens', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});

Expand Down
1 change: 0 additions & 1 deletion tests/e2e/tests/content-manager/editview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { findAndClose } from '../../utils/shared';
test.describe('Edit View', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});

Expand Down
1 change: 0 additions & 1 deletion tests/e2e/tests/content-manager/listview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
test.describe('List View', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ const addEntryToRelease = async ({ page, releaseName }: { page: Page; releaseNam
describeOnCondition(edition === 'EE')('Release page', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });

await page.getByRole('link', { name: 'Releases' }).click();
Expand Down
1 change: 0 additions & 1 deletion tests/e2e/tests/content-releases/releases-page.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE';
describeOnCondition(edition === 'EE')('Releases page', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ test.describe('Create collection type', () => {
test.beforeEach(async ({ page }) => {
await resetFiles();
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });

await page.getByRole('link', { name: 'Content-Type Builder' }).click();
Expand Down
1 change: 0 additions & 1 deletion tests/e2e/tests/content-type-builder/ctb-edit-view.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
test.describe('Edit View CTB', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});

Expand Down
1 change: 0 additions & 1 deletion tests/e2e/tests/content-type-builder/tutorial.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
test.describe('Tutorial', () => {
test.beforeEach(async ({ page }) => {
await resetDatabaseAndImportDataFromPath('with-admin.tar');
await page.goto('/admin');
await login({ page });
});

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
27 changes: 24 additions & 3 deletions tests/e2e/utils/login.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
import type { Page } from '@playwright/test';
import { ADMIN_EMAIL_ADDRESS, ADMIN_PASSWORD } from '../constants';
import { ADMIN_EMAIL_ADDRESS, ADMIN_PASSWORD, TITLE_HOME, URL_ROOT } from '../constants';

const REMEMBER_ME_LABEL = 'rememberMe';

/**
* Log in to an e2e test app
* Clear cookies, go to admin, and log in to an e2e test app, landing on the Strapi dashboard
*/
export const login = async ({ page, rememberMe = false }: { page: Page; rememberMe?: boolean }) => {
// sometimes it gets stuck on the login screen with an invalid session and can't log in
await page.context().clearCookies();

// go to the root page which should be the logged in
await page.goto(URL_ROOT, { waitUntil: 'networkidle' });

await page.getByLabel('Email').fill(ADMIN_EMAIL_ADDRESS);
await page
.getByLabel('Password*', {
Expand All @@ -13,8 +21,21 @@ export const login = async ({ page, rememberMe = false }: { page: Page; remember
.fill(ADMIN_PASSWORD);

if (rememberMe) {
await page.getByLabel('rememberMe').click();
await page.getByLabel(REMEMBER_ME_LABEL).click();
}

await page.getByRole('button', { name: 'Login' }).click();
await page.waitForLoadState('networkidle');

await waitForTitle(page, TITLE_HOME, 5000);
};

export const waitForTitle = async (page: Page, title: string, timeout = 5000) => {
for (let i = 0; i < timeout / 100; i++) {
if ((await page.title()) === title) {
return;
}
await page.waitForTimeout(100);
}
throw new Error(`Title did not change to ${title} after ${timeout}ms`);
};
File renamed without changes.
8 changes: 4 additions & 4 deletions tests/e2e/utils/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ export const navToHeader = async (page: Page, navItems: string[], headerText: st
// 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}"]`);
await expect(item).toBeVisible();
await item.click();
await item.waitFor({ state: 'visible' });
await Promise.all([item.click(), page.waitForLoadState('networkidle')]);
}

const header = page.getByRole('heading', { name: headerText, exact: true });
await expect(header).toBeVisible();
await header.waitFor({ state: 'visible' });
return header;
};

Expand All @@ -38,7 +38,7 @@ export const findAndClose = async (

// Find the 'Close' button that is a sibling of the element containing the specified text.
const closeBtn = await page.locator(
`:has-text("${text}")[role="${role}"] ~ button[aria-label="${closeLabel}"]`
`:has-text("${text}")[role="${role}"] ~ button:has-text("${closeLabel}")`
);

// Click the 'Close' button.
Expand Down