Skip to content

Commit

Permalink
test: add e2e tests for CTB restarts from file changes
Browse files Browse the repository at this point in the history
  • Loading branch information
innerdvations committed Apr 4, 2024
1 parent 79f8c92 commit 5ab818b
Show file tree
Hide file tree
Showing 21 changed files with 343 additions and 118 deletions.
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();
});

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

0 comments on commit 5ab818b

Please sign in to comment.