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

test: add e2e tests for CTB restarts from file changes #19801

Merged
merged 29 commits into from
Apr 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
4314943
doc: update e2e test docs
innerdvations Mar 15, 2024
b718823
fix: allow args to pass through to playwright
innerdvations Mar 15, 2024
a88266a
test: move transfer token tests
innerdvations Mar 15, 2024
3798695
test: init ctb tutorial test
innerdvations Mar 15, 2024
55f5cf9
test: init create collection type test
innerdvations Mar 15, 2024
b607cec
test: add waitForRestart
innerdvations Mar 15, 2024
a611443
test: add custom timeout to restart
innerdvations Mar 15, 2024
ad52634
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Mar 18, 2024
152443c
chore: update comment
innerdvations Mar 18, 2024
d6b62b2
enhancement: handle file system state management
innerdvations Mar 19, 2024
4429aee
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Mar 19, 2024
4e518f2
fix: add fake git user for test-apps
innerdvations Mar 19, 2024
6f8e577
test: fix import path
innerdvations Mar 19, 2024
28c3a85
test: add cleanup
innerdvations Mar 19, 2024
4c81f51
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Mar 27, 2024
5a65184
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Mar 28, 2024
1af9820
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Mar 29, 2024
9679c71
test: remove page from afterAll
innerdvations Mar 29, 2024
dfc5496
test: comment
innerdvations Mar 29, 2024
ae781e9
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Apr 2, 2024
aa5bfaa
chore: refactor utils
innerdvations Apr 3, 2024
b1b5606
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Apr 3, 2024
e42b1b5
fix: move delay to util
innerdvations Apr 3, 2024
0f9a701
Merge branch 'chore/e2e-ctb-tests' of https://github.com/strapi/strap…
innerdvations Apr 3, 2024
9d4d67e
docs: info on running individual files
innerdvations Apr 3, 2024
101d2da
fix: add fallback page reload
innerdvations Apr 3, 2024
0c2a5f0
fix: check strapi is defined
innerdvations Apr 3, 2024
718fd2c
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Apr 3, 2024
6c67881
Merge branch 'develop' into chore/e2e-ctb-tests
innerdvations Apr 4, 2024
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
18 changes: 15 additions & 3 deletions docs/docs/guides/e2e/00-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,27 @@ If you need to clean the test-apps folder because they are not working as expect
To run only one domain, meaning a top-level directory in e2e/tests such as "admin" or "content-manager", use the `--domains` option.

```shell
yarn test:e2e --domains admin
yarn test:e2e --domain admin
yarn test:e2e --domains=admin
npm run test:e2e --domains=admin
```

To run a specific file, you can pass arguments and options to playwright using `--` between the test:e2e options and the playwright options, such as:

```shell
# to run just the login.spec.ts file in the admin domain
yarn test:e2e --domains admin -- login.spec.ts
yarn test:e2e --domains=admin -- login.spec.ts
npm run test:e2e --domains=admin -- login.spec.ts
```

Note that you must still include a domain, otherwise playwright will attempt to run every domain filtering by that filename, and any domains that do not contain that filename will fail with "no tests found"

### Running specific browsers

To run only a specific browser (to speed up test development, for example) you can pass `--project` to playwright with the value(s) `chromium`, `firefox`, or `webkit`

```shell
yarn test:e2e --domains=admin -- login.spec.ts --project=chromium
npm run test:e2e --domains=admin -- login.spec.ts --project=chromium
```

### Concurrency / parallellization
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,14 @@ export const transformUpgradeHeader = (header = '') => {

let timeouts: Record<string, number> | undefined;

const hasHttpServer = () => {
// during server restarts, strapi may not have ever been defined at all, so we have to check it first
return typeof strapi !== 'undefined' && !!strapi?.server?.httpServer;
};

// temporarily disable server timeouts while transfer is running
const disableTimeouts = () => {
if (!strapi?.server?.httpServer) {
if (!hasHttpServer()) {
return;
}

Expand All @@ -45,7 +50,7 @@ const disableTimeouts = () => {
strapi.log.info('[Data transfer] Disabling http timeouts');
};
const resetTimeouts = () => {
if (!strapi?.server?.httpServer || !timeouts) {
if (!hasHttpServer() || !timeouts) {
return;
}

Expand Down
11 changes: 4 additions & 7 deletions playwright.base.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,6 @@ const createConfig = ({ port, testDir, appDir }) => ({

/* Default time each action such as `click()` can take to 20s */
actionTimeout: getEnvNum(process.env.PLAYWRIGHT_ACTION_TIMEOUT, 20 * 1000),

/* Collect trace when a test failed on the CI. See https://playwright.dev/docs/trace-viewer
Until https://github.com/strapi/strapi/issues/18196 is fixed we can't enable this locally,
because the Strapi server restarts every time a new file (trace) is created.
*/
trace: 'retain-on-failure',
video: getEnvBool(process.env.PLAYWRIGHT_VIDEO, false)
? {
Expand Down Expand Up @@ -105,8 +100,10 @@ const createConfig = ({ port, testDir, appDir }) => ({
},
],

/* Folder for test artifacts such as screenshots, videos, traces, etc. */
outputDir: getEnvString(process.env.PLAYWRIGHT_OUTPUT_DIR, '../test-results/'), // in the test-apps/e2e dir, to avoid writing files to the running Strapi project dir
/* Folder for test artifacts such as screenshots, videos, traces, etc.
* Must be outside the project itself or develop mode will restart when files are written
* */
outputDir: getEnvString(process.env.PLAYWRIGHT_OUTPUT_DIR, '../test-results/'),

/* Run your local dev server before starting the tests */
webServer: {
Expand Down
95 changes: 2 additions & 93 deletions tests/e2e/scripts/dts-export.js
Original file line number Diff line number Diff line change
@@ -1,95 +1,4 @@
const {
file: {
providers: { createLocalFileDestinationProvider },
},
strapi: {
providers: { createLocalStrapiSourceProvider },
},
engine: { createTransferEngine },
} = require('@strapi/data-transfer');
const strapiFactory = require('@strapi/strapi');
const { ALLOWED_CONTENT_TYPES } = require('../constants');

/**
* Export the data from a strapi project.
* This script should be run as `node <path-to>/dts-export.js [exportFilePath]` from the
* root directory of a strapi project e.g. `/examples/kitchensink`. Remember to import
* the `with-admin` tar file into the project first because the tests rely on the data.
*/
const exportData = async () => {
let args = process.argv.slice(2);

if (args.length !== 1) {
console.error('Please provide the export file name as a parameter.');
process.exit(1);
}

const strapi = await createStrapiInstance();

const source = createSourceProvider(strapi);
const destination = createDestinationProvider(args[0]);

const engine = createTransferEngine(source, destination, {
versionStrategy: 'ignore', // for an export to file, versionStrategy will always be skipped
schemaStrategy: 'ignore', // for an export to file, schemaStrategy will always be skipped
only: ['content', 'files'],
transforms: {
links: [
{
filter(link) {
return (
ALLOWED_CONTENT_TYPES.includes(link.left.type) &&
ALLOWED_CONTENT_TYPES.includes(link.right.type)
);
},
},
],
entities: [
{
filter(entity) {
return ALLOWED_CONTENT_TYPES.includes(entity.type);
},
},
],
},
});

engine.diagnostics.onDiagnostic(console.log);

try {
const results = await engine.transfer();

console.log(JSON.stringify(results.engine, null, 2));
} catch {
console.error('Export process failed.');
process.exit(1);
}

process.exit(0);
};

const createSourceProvider = (strapi) =>
createLocalStrapiSourceProvider({
async getStrapi() {
return strapi;
},
});

const createDestinationProvider = (filePath) =>
createLocalFileDestinationProvider({
file: { path: filePath },
encryption: { enabled: false },
compression: { enabled: false },
});

const createStrapiInstance = async (logLevel = 'error') => {
const appContext = await strapiFactory.compile();
const app = strapiFactory(appContext);

app.log.level = logLevel;
const loadedApp = await app.load();

return loadedApp;
};
const { exportData } = require('../utils/dts-export');

// TODO: make an actual yargs command and pass common options to exportData so it's easier to build the test data
exportData();
4 changes: 2 additions & 2 deletions tests/e2e/tests/admin/login.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test';
import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import';
import { toggleRateLimiting } from '../../scripts/rate-limit';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { toggleRateLimiting } from '../../utils/rate-limit';
import { ADMIN_EMAIL_ADDRESS, ADMIN_PASSWORD } from '../../constants';
import { login } from '../../utils/login';

Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/tests/admin/logout.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test';
// eslint-disable-next-line import/extensions
import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { login } from '../../utils/login';

test.describe('Log Out', () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/tests/admin/signup.spec.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test';

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

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/tests/admin/transfer/tokens.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test';
import { login } from '../../../utils/login';
import { resetDatabaseAndImportDataFromPath } from '../../../scripts/dts-import';
import { resetDatabaseAndImportDataFromPath } from '../../../utils/dts-import';
import { navToHeader } from '../../../utils/shared';

const createTransferToken = async (page, tokenName, duration, type) => {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/tests/content-manager/editview.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test';
import { login } from '../../utils/login';
import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { findAndClose } from '../../utils/shared';

test.describe('Edit View', () => {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/tests/content-manager/listview.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
*/
import { test, expect } from '@playwright/test';
import { login } from '../../utils/login';
import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';

test.describe('List View', () => {
test.beforeEach(async ({ page }) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect, type Page } from '@playwright/test';
import { describeOnCondition } from '../../utils/shared';
import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { login } from '../../utils/login';

const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE';
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/tests/content-releases/releases-page.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect } from '@playwright/test';
import { describeOnCondition } from '../../utils/shared';
import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
import { login } from '../../utils/login';

const edition = process.env.STRAPI_DISABLE_EE === 'true' ? 'CE' : 'EE';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { test, expect } from '@playwright/test';
import { login } from '../../../utils/login';
import { resetDatabaseAndImportDataFromPath } from '../../../utils/dts-import';
import { waitForRestart } from '../../../utils/restart';
import { resetFiles } from '../../../utils/file-reset';

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();

// close the tutorial modal if it's visible
const modal = page.getByRole('button', { name: 'Close' });
if (modal.isVisible()) {
await modal.click();
await expect(modal).not.toBeVisible();
}
});

// TODO: each test should have a beforeAll that does this, maybe combine all the setup into one util to simplify it
// to keep other suites that don't modify files from needing to reset files, clean up after ourselves at the end
test.afterAll(async () => {
await resetFiles();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are you doing it afterAll but also in beforeEach? Maybe it should be in afterEach instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a tricky question here.

In this file, the correct thing would be only the beforeEach (since each test makes changes) and then other suites would ensure on their own it was clean with a beforeAll/beforeEach as needed. But doing the filesystem reset is slow and only a couple suites make file changes (ctb and uploading), and I didn't want to update every other test file to do that.

So I put the "bad" code (trying to clean up for other tests in afterAll is bad practice because you're not guaranteed it will run) separate even though it means we run it one extra time, because ultimately I want to remove it altogether once we have a faster method to detect/reset the files that would be ok to add universally.

});

test('Can create a collection type', async ({ page }) => {
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('Secret Document');

const singularId = page.getByLabel('API ID (Singular)');
await expect(singularId).toHaveValue('secret-document');

const pluralId = page.getByLabel('API ID (Plural)');
await expect(pluralId).toHaveValue('secret-documents');

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

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: 'Secret Document' })).toBeVisible();
});
});
2 changes: 1 addition & 1 deletion tests/e2e/tests/content-type-builder/ctb-edit-view.spec.ts
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 { navToHeader } from '../../utils/shared';
import { resetDatabaseAndImportDataFromPath } from '../../scripts/dts-import';
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';

test.describe('Edit View CTB', () => {
test.beforeEach(async ({ page }) => {
Expand Down
26 changes: 26 additions & 0 deletions tests/e2e/tests/content-type-builder/tutorial.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { test, expect } from '@playwright/test';
import { login } from '../../utils/login';
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 });
});

test('Shows tutorial on first content type', async ({ page }) => {
await page.getByRole('link', { name: 'Content-type Builder' }).click();

const modalHeader = page.getByRole('heading', { name: '🧠 Create a first Collection' });
expect(modalHeader).toBeVisible();
await modalHeader.click();

const closeButton = page.getByRole('button', { name: 'Close' });
expect(closeButton).toBeVisible();
await closeButton.click();

await expect(closeButton).not.toBeVisible();
await expect(modalHeader).not.toBeVisible();
});
});