mirror of
https://github.com/strapi/strapi.git
synced 2025-06-27 00:41:25 +00:00
test(e2e): improve flakiness and performance of file reset (#23337)
This commit is contained in:
parent
76977370da
commit
df1dc7b498
9
.github/workflows/tests.yml
vendored
9
.github/workflows/tests.yml
vendored
@ -212,17 +212,13 @@ jobs:
|
||||
shard: [1/4, 2/4, 3/4, 4/4]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Monorepo install
|
||||
uses: ./.github/actions/yarn-nm-install
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
|
||||
@ -277,7 +273,6 @@ jobs:
|
||||
|
||||
- name: Monorepo build
|
||||
uses: ./.github/actions/run-build
|
||||
|
||||
- name: Run [EE] E2E tests
|
||||
uses: ./.github/actions/run-e2e-tests
|
||||
with:
|
||||
@ -439,7 +434,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node: [20, 22]
|
||||
shard: [1/4, 2/4, 3/4, 4/4]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
services:
|
||||
postgres:
|
||||
image: postgres
|
||||
@ -482,7 +477,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
node: [20, 22]
|
||||
shard: [1/4, 2/4, 3/4, 4/4]
|
||||
shard: [1/5, 2/5, 3/5, 4/5, 5/5]
|
||||
services:
|
||||
mysql:
|
||||
image: bitnami/mysql:latest
|
||||
|
@ -56,7 +56,7 @@
|
||||
"setup": "yarn && yarn clean && yarn build --skip-nx-cache",
|
||||
"test:api": "node tests/scripts/run-api-tests.js",
|
||||
"test:api:clean": "rimraf ./coverage",
|
||||
"test:clean": "yarn test:api:clean ; yarn test:e2e:clean ; yarn test:cli:clean",
|
||||
"test:clean": "run-s -c test:api:clean test:e2e:clean test:cli:clean",
|
||||
"test:cli": "node tests/scripts/run-cli-tests.js",
|
||||
"test:cli:clean": "node tests/scripts/run-cli-tests.js clean",
|
||||
"test:cli:debug": "node tests/scripts/run-cli-tests.js --debug",
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { clickAndWait, dragElementAbove, findAndClose, isElementBefore } from '../../utils/shared';
|
||||
import {
|
||||
clickAndWait,
|
||||
dragElementAbove,
|
||||
findAndClose,
|
||||
isElementBefore,
|
||||
navToHeader,
|
||||
} from '../../utils/shared';
|
||||
import { createContent, FieldValue, verifyFields } from '../../utils/content-creation';
|
||||
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
|
||||
import { login } from '../../utils/login';
|
||||
@ -10,8 +16,7 @@ test.describe('Adding content', () => {
|
||||
await page.goto('/admin');
|
||||
await login({ page });
|
||||
|
||||
// Navigate to Content Manager
|
||||
await clickAndWait(page, page.getByRole('link', { name: 'Content Manager' }));
|
||||
await navToHeader(page, ['Content Manager'], 'Content Manager');
|
||||
});
|
||||
|
||||
test('I want to be able to save and publish content', async ({ page }) => {
|
||||
|
@ -6,7 +6,7 @@ import { clickAndWait } from '../../../utils/shared';
|
||||
|
||||
test.describe('Create collection type with all field types', () => {
|
||||
// very long timeout for these tests because they restart the server multiple times
|
||||
test.describe.configure({ timeout: 300000 });
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await sharedSetup('ctb-edit-ct', page, {
|
||||
@ -19,8 +19,6 @@ test.describe('Create collection type with all field types', () => {
|
||||
await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' }));
|
||||
});
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import { sharedSetup } from '../../../utils/setup';
|
||||
|
||||
test.describe('Edit collection type', () => {
|
||||
// very long timeout for these tests because they restart the server multiple times
|
||||
test.describe.configure({ timeout: 300000 });
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
// use existing type to avoid extra resets and flakiness
|
||||
const ctName = 'Article';
|
||||
@ -23,9 +23,7 @@ test.describe('Edit collection type', () => {
|
||||
await navToHeader(page, ['Content-Type Builder', ctName], ctName);
|
||||
});
|
||||
|
||||
// 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.afterEach(async () => {
|
||||
test.afterAll(async () => {
|
||||
await resetFiles();
|
||||
});
|
||||
|
||||
|
@ -5,7 +5,7 @@ import { createComponent, type AddAttribute } from '../../../utils/content-types
|
||||
|
||||
test.describe('Create a new component', () => {
|
||||
// very long timeout for these tests because they restart the server multiple times
|
||||
test.describe.configure({ timeout: 300000 });
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await sharedSetup('create-component', page, {
|
||||
|
@ -14,7 +14,7 @@ import { navToHeader } from '../../../utils/shared';
|
||||
|
||||
test.describe('Update a new component', () => {
|
||||
// very long timeout for these tests because they restart the server multiple times
|
||||
test.describe.configure({ timeout: 300000 });
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
const originalAttributes = [{ type: 'text', name: 'testtext' }] satisfies AddAttribute[];
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { clickAndWait } from '../../../utils/shared';
|
||||
|
||||
test.describe('Create single type with all field types', () => {
|
||||
// very long timeout for these tests because they restart the server multiple times
|
||||
test.describe.configure({ timeout: 300000 });
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await sharedSetup('ctb-edit-st', page, {
|
||||
@ -23,8 +23,6 @@ test.describe('Create single type with all field types', () => {
|
||||
await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' }));
|
||||
});
|
||||
|
||||
// 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();
|
||||
});
|
||||
|
@ -6,7 +6,7 @@ import { sharedSetup } from '../../../utils/setup';
|
||||
|
||||
test.describe('Edit single type', () => {
|
||||
// very long timeout for these tests because they restart the server multiple times
|
||||
test.describe.configure({ timeout: 300000 });
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
// Use the existing single-type from our test data
|
||||
const ctName = 'Homepage';
|
||||
@ -23,9 +23,7 @@ test.describe('Edit single type', () => {
|
||||
await navToHeader(page, ['Content-Type Builder', ctName], ctName);
|
||||
});
|
||||
|
||||
// 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.afterEach(async () => {
|
||||
test.afterAll(async () => {
|
||||
await resetFiles();
|
||||
});
|
||||
|
||||
|
244
tests/e2e/tests/i18n/create-edit.spec.ts
Normal file
244
tests/e2e/tests/i18n/create-edit.spec.ts
Normal file
@ -0,0 +1,244 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { login } from '../../utils/login';
|
||||
import { clickAndWait, findAndClose, navToHeader } from '../../utils/shared';
|
||||
import { waitForRestart } from '../../utils/restart';
|
||||
import { resetFiles } from '../../utils/file-reset';
|
||||
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
|
||||
|
||||
test.describe('Create and Edit Operations', () => {
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await resetDatabaseAndImportDataFromPath('with-admin.tar');
|
||||
await page.goto('/admin');
|
||||
await login({ page });
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await resetFiles();
|
||||
});
|
||||
|
||||
test('As a user I want to create a brand new document in the non-default locale', async ({
|
||||
page,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::product.product(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::product.product\/[^/]+(\?.*)?/;
|
||||
const CREATE_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::product.product\/create(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our products list-view
|
||||
*/
|
||||
await navToHeader(page, ['Content Manager', 'Products'], 'Products');
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
*/
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'English (en)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Swap to es locale to create a new document
|
||||
*/
|
||||
await page.getByRole('combobox', { name: 'Select a locale' }).click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'Spanish (es)'
|
||||
);
|
||||
await expect(page.getByRole('row', { name: 'No content found' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* So now we're going to create a document.
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Create new entry' }).first().click();
|
||||
await page.waitForURL(CREATE_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Create an entry' })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: 'Locales' })).toHaveText('Spanish (es)');
|
||||
expect(new URL(page.url()).searchParams.get('plugins[i18n][locale]')).toEqual('es');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'name' })
|
||||
.fill('Camiseta de fuera 23/24 de Nike para hombres');
|
||||
|
||||
/**
|
||||
* Verify the UID works as expected
|
||||
*/
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const requestPromise = page.waitForRequest('**/content-manager/uid/generate?locale=es');
|
||||
await page.getByRole('button', { name: 'Regenerate' }).click();
|
||||
const req = await requestPromise;
|
||||
return req.postDataJSON();
|
||||
})
|
||||
.toMatchObject({
|
||||
contentTypeUID: 'api::product.product',
|
||||
data: {
|
||||
id: '',
|
||||
isAvailable: true,
|
||||
name: 'Camiseta de fuera 23/24 de Nike para hombres',
|
||||
slug: 'product',
|
||||
},
|
||||
field: 'slug',
|
||||
});
|
||||
await expect(page.getByRole('textbox', { name: 'slug' })).toHaveValue(
|
||||
'camiseta-de-fuera-23-24-de-nike-para-hombres'
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish the document
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Publish' }).click();
|
||||
await findAndClose(page, 'Success:Published');
|
||||
|
||||
/**
|
||||
* Now we'll go back to the list view to ensure the content has been updated
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Products' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'Spanish (es)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Now we'll go back to the edit view to swap back to the en locale to ensure
|
||||
* these updates were made on a different document
|
||||
*/
|
||||
await page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' }).click();
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||
await page.getByRole('option', { name: 'English (en)' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Untitled' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('As a user I want to add a locale entry to an existing document', async ({
|
||||
browser,
|
||||
page,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::product.product(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::product.product\/[^/]+(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our products list-view where there will be one document already made in the `en` locale
|
||||
*/
|
||||
await navToHeader(page, ['Content Manager', 'Products'], 'Products');
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
*/
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'English (en)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('row', { name: 'Nike Mens 23/24 Away Stadium Jersey' }).click();
|
||||
|
||||
/**
|
||||
* Assert we're on the edit view for the document
|
||||
*/
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
|
||||
/**
|
||||
* Now we should be on a new document in the `es` locale
|
||||
*/
|
||||
expect(new URL(page.url()).searchParams.get('plugins[i18n][locale]')).toEqual('es');
|
||||
await expect(page.getByRole('heading', { name: 'Untitled' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* This is here because the `fill` method below doesn't immediately update the value
|
||||
* in webkit.
|
||||
*/
|
||||
if (browser.browserType().name() === 'webkit') {
|
||||
await page.getByRole('textbox', { name: 'name' }).press('s');
|
||||
await page.getByRole('textbox', { name: 'name' }).press('Delete');
|
||||
}
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'name' })
|
||||
.fill('Camiseta de fuera 23/24 de Nike para hombres');
|
||||
|
||||
/**
|
||||
* Verify the UID works as expected due to issues with webkit above,
|
||||
* this has been kept.
|
||||
*/
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const requestPromise = page.waitForRequest('**/content-manager/uid/generate?locale=es');
|
||||
await page.getByRole('button', { name: 'Regenerate' }).click();
|
||||
const body = (await requestPromise).postDataJSON();
|
||||
return body;
|
||||
},
|
||||
{
|
||||
intervals: [1000, 2000, 4000, 8000],
|
||||
}
|
||||
)
|
||||
.toMatchObject({
|
||||
contentTypeUID: 'api::product.product',
|
||||
data: {
|
||||
id: expect.any(String),
|
||||
name: 'Camiseta de fuera 23/24 de Nike para hombres',
|
||||
slug: 'product',
|
||||
},
|
||||
field: 'slug',
|
||||
});
|
||||
|
||||
await expect(page.getByRole('textbox', { name: 'slug' })).toHaveValue(
|
||||
'camiseta-de-fuera-23-24-de-nike-para-hombres'
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish the document
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Publish' }).click();
|
||||
await findAndClose(page, 'Success:Published');
|
||||
|
||||
/**
|
||||
* Now we'll go back to the list view to ensure the content has been updated
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Products' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'Spanish (es)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Now we'll go back to the edit view to swap back to the en locale to ensure
|
||||
* these updates were made on the same document
|
||||
*/
|
||||
await page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' }).click();
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||
await page.getByRole('option', { name: 'English (en)' }).click();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
});
|
||||
});
|
@ -3,13 +3,11 @@ import { test, expect } from '@playwright/test';
|
||||
import { EDITOR_EMAIL_ADDRESS, EDITOR_PASSWORD } from '../../constants';
|
||||
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
|
||||
import { login } from '../../utils/login';
|
||||
import { clickAndWait, findAndClose } from '../../utils/shared';
|
||||
import { clickAndWait, findAndClose, navToHeader } from '../../utils/shared';
|
||||
import { waitForRestart } from '../../utils/restart';
|
||||
|
||||
test.describe('Edit view', () => {
|
||||
// TODO: split this into multiple tests
|
||||
// give additional time because this test file is so large
|
||||
test.describe.configure({ timeout: 300000 });
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await resetDatabaseAndImportDataFromPath('with-admin.tar');
|
||||
@ -17,370 +15,11 @@ test.describe('Edit view', () => {
|
||||
await login({ page });
|
||||
});
|
||||
|
||||
test('As a user I want to create a brand new document in the non-default locale', async ({
|
||||
page,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::product.product(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::product.product\/[^/]+(\?.*)?/;
|
||||
const CREATE_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::product.product\/create(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our products list-view
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.getByRole('link', { name: 'Products' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
*/
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'English (en)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Swap to es locale to create a new document
|
||||
*/
|
||||
await page.getByRole('combobox', { name: 'Select a locale' }).click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'Spanish (es)'
|
||||
);
|
||||
await expect(page.getByRole('row', { name: 'No content found' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* So now we're going to create a document.
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Create new entry' }).first().click();
|
||||
await page.waitForURL(CREATE_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Create an entry' })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: 'Locales' })).toHaveText('Spanish (es)');
|
||||
expect(new URL(page.url()).searchParams.get('plugins[i18n][locale]')).toEqual('es');
|
||||
await page
|
||||
.getByRole('textbox', { name: 'name' })
|
||||
.fill('Camiseta de fuera 23/24 de Nike para hombres');
|
||||
|
||||
/**
|
||||
* Verify the UID works as expected
|
||||
*/
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const requestPromise = page.waitForRequest('**/content-manager/uid/generate?locale=es');
|
||||
await page.getByRole('button', { name: 'Regenerate' }).click();
|
||||
const req = await requestPromise;
|
||||
return req.postDataJSON();
|
||||
})
|
||||
.toMatchObject({
|
||||
contentTypeUID: 'api::product.product',
|
||||
data: {
|
||||
id: '',
|
||||
isAvailable: true,
|
||||
name: 'Camiseta de fuera 23/24 de Nike para hombres',
|
||||
slug: 'product',
|
||||
},
|
||||
field: 'slug',
|
||||
});
|
||||
await expect(page.getByRole('textbox', { name: 'slug' })).toHaveValue(
|
||||
'camiseta-de-fuera-23-24-de-nike-para-hombres'
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish the document
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Publish' }).click();
|
||||
await findAndClose(page, 'Success:Published');
|
||||
|
||||
/**
|
||||
* Now we'll go back to the list view to ensure the content has been updated
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Products' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'Spanish (es)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Now we'll go back to the edit view to swap back to the en locale to ensure
|
||||
* these updates were made on a different document
|
||||
*/
|
||||
await page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' }).click();
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||
await page.getByRole('option', { name: 'English (en)' }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Untitled' })).toBeVisible();
|
||||
});
|
||||
|
||||
test('As a user I want to add a locale entry to an existing document', async ({
|
||||
browser,
|
||||
page,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::product.product(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::product.product\/[^/]+(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our products list-view where there will be one document already made in the `en` locale
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.getByRole('link', { name: 'Products' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
*/
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'English (en)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('row', { name: 'Nike Mens 23/24 Away Stadium Jersey' }).click();
|
||||
|
||||
/**
|
||||
* Assert we're on the edit view for the document
|
||||
*/
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
|
||||
/**
|
||||
* Now we should be on a new document in the `es` locale
|
||||
*/
|
||||
expect(new URL(page.url()).searchParams.get('plugins[i18n][locale]')).toEqual('es');
|
||||
await expect(page.getByRole('heading', { name: 'Untitled' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* This is here because the `fill` method below doesn't immediately update the value
|
||||
* in webkit.
|
||||
*/
|
||||
if (browser.browserType().name() === 'webkit') {
|
||||
await page.getByRole('textbox', { name: 'name' }).press('s');
|
||||
await page.getByRole('textbox', { name: 'name' }).press('Delete');
|
||||
}
|
||||
|
||||
await page
|
||||
.getByRole('textbox', { name: 'name' })
|
||||
.fill('Camiseta de fuera 23/24 de Nike para hombres');
|
||||
|
||||
/**
|
||||
* Verify the UID works as expected due to issues with webkit above,
|
||||
* this has been kept.
|
||||
*/
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const requestPromise = page.waitForRequest('**/content-manager/uid/generate?locale=es');
|
||||
await page.getByRole('button', { name: 'Regenerate' }).click();
|
||||
const body = (await requestPromise).postDataJSON();
|
||||
return body;
|
||||
},
|
||||
{
|
||||
intervals: [1000, 2000, 4000, 8000],
|
||||
}
|
||||
)
|
||||
.toMatchObject({
|
||||
contentTypeUID: 'api::product.product',
|
||||
data: {
|
||||
id: expect.any(String),
|
||||
name: 'Camiseta de fuera 23/24 de Nike para hombres',
|
||||
slug: 'product',
|
||||
},
|
||||
field: 'slug',
|
||||
});
|
||||
|
||||
await expect(page.getByRole('textbox', { name: 'slug' })).toHaveValue(
|
||||
'camiseta-de-fuera-23-24-de-nike-para-hombres'
|
||||
);
|
||||
|
||||
/**
|
||||
* Publish the document
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Publish' }).click();
|
||||
await findAndClose(page, 'Success:Published');
|
||||
|
||||
/**
|
||||
* Now we'll go back to the list view to ensure the content has been updated
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Products' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Products' })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'Spanish (es)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
|
||||
/**
|
||||
* Now we'll go back to the edit view to swap back to the en locale to ensure
|
||||
* these updates were made on the same document
|
||||
*/
|
||||
await page.getByRole('row', { name: 'Camiseta de fuera 23/24 de Nike para hombres' }).click();
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Camiseta de fuera 23/24 de Nike para hombres' })
|
||||
).toBeVisible();
|
||||
await page.getByRole('combobox', { name: 'Locales' }).click();
|
||||
await page.getByRole('option', { name: 'English (en)' }).click();
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Nike Mens 23/24 Away Stadium Jersey' })
|
||||
).toBeVisible();
|
||||
});
|
||||
|
||||
test("As a user I should not be able to create a document in a locale I don't have permissions for", async ({
|
||||
page,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to settings and roles & modify editor permissions
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Settings', exact: true }).click();
|
||||
await page.getByRole('link', { name: 'Roles' }).first().click();
|
||||
await page.getByRole('gridcell', { name: 'Editor', exact: true }).click();
|
||||
|
||||
/**
|
||||
* Set permissions for English (en) locale
|
||||
*/
|
||||
await page.getByRole('button', { name: 'Article' }).click();
|
||||
await page.getByLabel('Select all English (en)').check();
|
||||
|
||||
/**
|
||||
* Set permissions for French (fr) locale. Editors can now do everything BUT
|
||||
* create french content
|
||||
*/
|
||||
await page.getByLabel('Select all French (fr)').check();
|
||||
await page.getByLabel('Select fr Create permission').uncheck();
|
||||
|
||||
// Scroll to the top of the page before clicking save
|
||||
// TODO: Fix the need to scroll to the top before saving. z-index of layout
|
||||
// header is behind the permissions component.
|
||||
await page.evaluate(() => window.scrollTo(0, 0));
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Logout and login as editor
|
||||
*/
|
||||
await page.getByRole('button', { name: 'tt test testing' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Log out' }).click();
|
||||
|
||||
await login({ page, username: EDITOR_EMAIL_ADDRESS, password: EDITOR_PASSWORD });
|
||||
|
||||
/**
|
||||
* Verify permissions
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByText('English (en)', { exact: true })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Verify we can create a new entry in the english locale as expected
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Create new entry' }).click();
|
||||
await page.getByLabel('title').fill('the richmond way');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Verify we cannot create a new entry in the french locale as editors do
|
||||
* not have the right permissions
|
||||
*/
|
||||
await page.getByLabel('Locales').click();
|
||||
await expect(page.getByLabel('Create French (fr) locale')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('As a user I should be able to delete a locale of a single type and collection type', async ({
|
||||
page,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/;
|
||||
const HOMEPAGE_LIST_URL =
|
||||
/\/admin\/content-manager\/single-types\/api::homepage.homepage(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our articles list-view and create a new entry
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await page.getByRole('link', { name: 'Create new entry' }).click();
|
||||
await page.getByLabel('title').fill('trent crimm');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Create a Spanish (es) locale for the entry
|
||||
*/
|
||||
await page.getByLabel('Locales').click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
await page.getByLabel('title').fill('dani rojas');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Delete the Spanish (es) locale entry
|
||||
*/
|
||||
await page.getByRole('button', { name: 'More actions' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Delete entry (Spanish (es))' }).click();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await findAndClose(page, 'Success:Deleted');
|
||||
|
||||
/**
|
||||
* Navigate to our homepage single-type and create a new entry
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.getByRole('link', { name: 'Homepage' }).click();
|
||||
await page.waitForURL(HOMEPAGE_LIST_URL);
|
||||
await page.getByLabel('title').fill('football is life');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Create a Spanish (es) locale for the homepage entry
|
||||
*/
|
||||
await page.getByLabel('Locales').click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
await page.getByLabel('title').fill('el fútbol también es muerte.');
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Delete the Spanish (es) locale homepage entry
|
||||
*/
|
||||
await page.getByRole('button', { name: 'More actions' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Delete entry (Spanish (es))' }).click();
|
||||
await page.getByRole('button', { name: 'Confirm' }).click();
|
||||
await findAndClose(page, 'Success:Deleted');
|
||||
});
|
||||
|
||||
test('As a user I want to publish multiple locales of my document', async ({ page, browser }) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our articles list-view where there will be one document already made in the `en` locale
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.getByRole('link', { name: 'Article' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible();
|
||||
await navToHeader(page, ['Content Manager', 'Article'], 'Article');
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
@ -396,7 +35,6 @@ test.describe('Edit view', () => {
|
||||
/**
|
||||
* Create a new spanish draft article
|
||||
*/
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Why I prefer football over soccer' })
|
||||
).toBeVisible();
|
||||
@ -456,17 +94,10 @@ test.describe('Edit view', () => {
|
||||
page,
|
||||
browser,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/;
|
||||
|
||||
/**
|
||||
* Navigate to our articles list-view where there will be one document already made in the `en` locale
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.getByRole('link', { name: 'Article' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible();
|
||||
await navToHeader(page, ['Content Manager', 'Article'], 'Article');
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
@ -482,7 +113,6 @@ test.describe('Edit view', () => {
|
||||
/**
|
||||
* Create a new spanish draft article
|
||||
*/
|
||||
await page.waitForURL(EDIT_URL);
|
||||
await expect(
|
||||
page.getByRole('heading', { name: 'Why I prefer football over soccer' })
|
||||
).toBeVisible();
|
||||
@ -553,184 +183,4 @@ test.describe('Edit view', () => {
|
||||
page.getByLabel('Unpublish multiple locales').getByRole('button', { name: 'Unpublish' })
|
||||
).toBeDisabled();
|
||||
});
|
||||
|
||||
interface ValidationType {
|
||||
field: string;
|
||||
initialValue: string;
|
||||
expectedError: string;
|
||||
ctbParams: {
|
||||
key: string;
|
||||
operation: {
|
||||
type: 'click' | 'fill';
|
||||
value?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const typesOfValidation: Record<string, ValidationType> = {
|
||||
required: {
|
||||
field: 'title',
|
||||
initialValue: '',
|
||||
ctbParams: {
|
||||
key: 'Required Field',
|
||||
operation: {
|
||||
type: 'click',
|
||||
},
|
||||
},
|
||||
expectedError: 'This value is required.',
|
||||
},
|
||||
maxLength: {
|
||||
field: 'title',
|
||||
initialValue: 'a'.repeat(256),
|
||||
ctbParams: {
|
||||
key: 'Maximum Length',
|
||||
operation: {
|
||||
type: 'fill',
|
||||
value: '255',
|
||||
},
|
||||
},
|
||||
expectedError: 'The value is too long',
|
||||
},
|
||||
// TODO schema changes from previous runs persist which means each new
|
||||
// validation must take into account the previous one.
|
||||
minLength: {
|
||||
field: 'title',
|
||||
initialValue: 'a'.repeat(10),
|
||||
ctbParams: {
|
||||
key: 'Minimum Length',
|
||||
operation: {
|
||||
type: 'fill',
|
||||
value: '11',
|
||||
},
|
||||
},
|
||||
expectedError: 'The value is too short',
|
||||
},
|
||||
};
|
||||
|
||||
for (const [type, validationParams] of Object.entries(typesOfValidation)) {
|
||||
test(`As a user I want to see the relevant error message when trying to publish a draft that fails ${type} validation`, async ({
|
||||
browser,
|
||||
page,
|
||||
}) => {
|
||||
const LIST_URL = /\/admin\/content-manager\/collection-types\/api::article.article(\?.*)?/;
|
||||
const EDIT_URL =
|
||||
/\/admin\/content-manager\/collection-types\/api::article.article\/[^/]+(\?.*)?/;
|
||||
|
||||
const {
|
||||
field,
|
||||
initialValue,
|
||||
ctbParams: { key: ctbKey, operation: ctbOperation },
|
||||
expectedError,
|
||||
} = validationParams;
|
||||
|
||||
/**
|
||||
* Navigate to our articles list-view where there will be one document already made in the `en` locale
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await clickAndWait(page, page.getByRole('link', { name: 'Article' }));
|
||||
await page.waitForURL(LIST_URL);
|
||||
await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Assert we're on the english locale and our document exists
|
||||
*/
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'English (en)'
|
||||
);
|
||||
await expect(
|
||||
page.getByRole('row', { name: 'Why I prefer football over soccer' })
|
||||
).toBeVisible();
|
||||
await page.getByText('why-i-prefer-football-over-').click();
|
||||
await page.waitForURL(EDIT_URL);
|
||||
|
||||
/**
|
||||
* This is here because the `fill` method below doesn't immediately update the value
|
||||
* in webkit.
|
||||
*/
|
||||
if (browser.browserType().name() === 'webkit') {
|
||||
await page.getByRole('textbox', { name: 'title' }).press('s');
|
||||
await page.getByRole('textbox', { name: 'title' }).press('Delete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the target field with the initial value, there is currently no
|
||||
* validation on this field
|
||||
*/
|
||||
await page.getByRole('textbox', { name: field }).fill(initialValue);
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Navigate to the CTB and modify the schema of the article to apply the
|
||||
* validation constraints to the field
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content-Type Builder' }).click();
|
||||
await page.getByRole('button', { name: 'Close' }).click(); // TODO improve this
|
||||
|
||||
/**
|
||||
* Edit the field and apply the validation constraint
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Article' }).click();
|
||||
await page
|
||||
.getByRole('button', { name: `Edit ${field}` })
|
||||
.first()
|
||||
.click();
|
||||
await page.getByRole('tab', { name: 'Advanced settings' }).click();
|
||||
|
||||
const ctbOperatonType = ctbOperation?.type ?? '';
|
||||
switch (ctbOperatonType) {
|
||||
case 'click':
|
||||
await page.getByLabel(ctbKey).first().click();
|
||||
break;
|
||||
case 'fill': {
|
||||
if (!ctbOperation?.value) {
|
||||
throw new Error('CTB operation value is required');
|
||||
}
|
||||
|
||||
await page.getByLabel(ctbKey).first().click();
|
||||
await page.getByRole('textbox', { name: ctbKey }).fill(ctbOperation.value);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new Error(`Unsupported CTB operation type ${ctbOperatonType} provided`);
|
||||
}
|
||||
|
||||
await page.getByRole('button', { name: 'Finish' }).click();
|
||||
await page.getByRole('button', { name: 'Save' }).click();
|
||||
|
||||
/**
|
||||
* Wait for the server to restart
|
||||
*/
|
||||
await waitForRestart(page);
|
||||
|
||||
/**
|
||||
* Navigate back to the article we just modified
|
||||
*/
|
||||
await page.getByRole('link', { name: 'Content Manager' }).click();
|
||||
await page.getByRole('link', { name: 'Article' }).click();
|
||||
await page.waitForURL(LIST_URL);
|
||||
|
||||
await expect(page.getByRole('heading', { name: 'Article' })).toBeVisible();
|
||||
await expect(page.getByRole('combobox', { name: 'Select a locale' })).toHaveText(
|
||||
'English (en)'
|
||||
);
|
||||
|
||||
/**
|
||||
* Attempt to publish through the 'Publish multiple locales' button
|
||||
*/
|
||||
await page.getByText('why-i-prefer-football-over-').click();
|
||||
await page.getByRole('button', { name: 'More document actions' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Publish multiple locales', exact: true }).click();
|
||||
|
||||
/**
|
||||
* We have modified the content and then modifed the schema in a way that
|
||||
* is incompatible. Therefore we should expect the relevant error message
|
||||
* to be displayed.
|
||||
*/
|
||||
await expect(page.getByText('1 entry waiting for action')).toBeVisible();
|
||||
await expect(
|
||||
page.getByLabel('Publish multiple locales').getByText(`${field}: ${expectedError}`)
|
||||
).toBeVisible();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
139
tests/e2e/tests/i18n/permissions.spec.ts
Normal file
139
tests/e2e/tests/i18n/permissions.spec.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
import { EDITOR_EMAIL_ADDRESS, EDITOR_PASSWORD } from '../../constants';
|
||||
import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import';
|
||||
import { login } from '../../utils/login';
|
||||
import { clickAndWait, findAndClose, navToHeader } from '../../utils/shared';
|
||||
import { waitForRestart } from '../../utils/restart';
|
||||
import { resetFiles } from '../../utils/file-reset';
|
||||
|
||||
test.describe('Locale Permissions', () => {
|
||||
test.describe.configure({ timeout: 500000 });
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await resetDatabaseAndImportDataFromPath('with-admin.tar');
|
||||
await page.goto('/admin');
|
||||
await login({ page });
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
await resetFiles();
|
||||
});
|
||||
|
||||
test("As a user I should not be able to create a document in a locale I don't have permissions for", async ({
|
||||
page,
|
||||
}) => {
|
||||
/**
|
||||
* Navigate to settings and roles & modify editor permissions
|
||||
*/
|
||||
await navToHeader(page, ['Settings', ['Administration Panel', 'Roles']], 'Roles');
|
||||
await expect(page.getByRole('gridcell', { name: 'Editor', exact: true })).toBeVisible();
|
||||
await page.getByRole('gridcell', { name: 'Editor', exact: true }).click();
|
||||
await expect(page.getByRole('heading', { name: 'Edit a role' })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Set permissions for English (en) locale
|
||||
*/
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Article' }));
|
||||
await page.getByLabel('Select all English (en)').check();
|
||||
|
||||
/**
|
||||
* Set permissions for French (fr) locale. Editors can now do everything BUT
|
||||
* create french content
|
||||
*/
|
||||
await page.getByLabel('Select all French (fr)').check();
|
||||
await page.getByLabel('Select fr Create permission').uncheck();
|
||||
|
||||
// Scroll to the top of the page before clicking save
|
||||
// TODO: Fix the need to scroll to the top before saving. z-index of layout
|
||||
// header is behind the permissions component.
|
||||
await page.evaluate(() => window.scrollTo(0, 0));
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Save' }));
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Logout and login as editor
|
||||
*/
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'tt test testing' }));
|
||||
await page.getByRole('menuitem', { name: /^Log(?:out| out)$/i }).click();
|
||||
await page.waitForURL('/admin/auth/login');
|
||||
|
||||
await login({ page, username: EDITOR_EMAIL_ADDRESS, password: EDITOR_PASSWORD });
|
||||
|
||||
/**
|
||||
* Verify permissions
|
||||
*/
|
||||
await navToHeader(page, ['Content Manager'], 'Content Manager');
|
||||
await expect(page.getByText('English (en)', { exact: true })).toBeVisible();
|
||||
|
||||
/**
|
||||
* Verify we can create a new entry in the english locale as expected
|
||||
*/
|
||||
await clickAndWait(page, page.getByRole('link', { name: 'Create new entry' }));
|
||||
await page.getByLabel('title').fill('the richmond way');
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Save' }));
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Verify we cannot create a new entry in the french locale as editors do
|
||||
* not have the right permissions
|
||||
*/
|
||||
await page.getByLabel('Locales').click();
|
||||
await expect(page.getByLabel('Create French (fr) locale')).toBeDisabled();
|
||||
});
|
||||
|
||||
test('As a user I should be able to delete a locale of a single type and collection type', async ({
|
||||
page,
|
||||
}) => {
|
||||
/**
|
||||
* Navigate to our articles list-view and create a new entry
|
||||
*/
|
||||
await navToHeader(page, ['Content Manager'], 'Content Manager');
|
||||
await clickAndWait(page, page.getByRole('link', { name: 'Create new entry' }));
|
||||
await page.getByLabel('title').fill('trent crimm');
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Save' }));
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Create a Spanish (es) locale for the entry
|
||||
*/
|
||||
await page.getByLabel('Locales').click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
await page.getByLabel('title').fill('dani rojas');
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Save' }));
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Delete the Spanish (es) locale entry
|
||||
*/
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'More actions' }));
|
||||
await clickAndWait(page, page.getByRole('menuitem', { name: 'Delete entry (Spanish (es))' }));
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Confirm' }));
|
||||
await findAndClose(page, 'Success:Deleted');
|
||||
|
||||
/**
|
||||
* Navigate to our homepage single-type and create a new entry
|
||||
*/
|
||||
await navToHeader(page, ['Content Manager', 'Homepage'], 'Homepage');
|
||||
await page.getByLabel('title').fill('football is life');
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Save' }));
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Create a Spanish (es) locale for the homepage entry
|
||||
*/
|
||||
await page.getByLabel('Locales').click();
|
||||
await page.getByRole('option', { name: 'Spanish (es)' }).click();
|
||||
await page.getByLabel('title').fill('el fútbol también es muerte.');
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Save' }));
|
||||
await findAndClose(page, 'Success:Saved');
|
||||
|
||||
/**
|
||||
* Delete the Spanish (es) locale homepage entry
|
||||
*/
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'More actions' }));
|
||||
await clickAndWait(page, page.getByRole('menuitem', { name: 'Delete entry (Spanish (es))' }));
|
||||
await clickAndWait(page, page.getByRole('button', { name: 'Confirm' }));
|
||||
await findAndClose(page, 'Success:Deleted');
|
||||
});
|
||||
});
|
@ -245,6 +245,105 @@ module.exports = config
|
||||
cwd: testAppPath,
|
||||
});
|
||||
|
||||
// We need to generate the typescript and documentation files to avoid re-generating after each file reset
|
||||
|
||||
// Start Strapi and wait for it to be ready
|
||||
console.log(`Starting Strapi for domain '${domain}' to generate files...`);
|
||||
const strapiProcess = execa('npm', ['run', 'develop', '--', '--no-watch-admin'], {
|
||||
cwd: testAppPath,
|
||||
env: {
|
||||
PORT: port,
|
||||
STRAPI_DISABLE_EE: !process.env.STRAPI_LICENSE,
|
||||
},
|
||||
detached: true, // This is important for CI
|
||||
});
|
||||
|
||||
// Wait for Strapi to be ready by checking HTTP endpoint
|
||||
await new Promise((resolve, reject) => {
|
||||
const startTime = Date.now();
|
||||
const timeout = 160 * 1000; // 160 seconds, matching Playwright's timeout
|
||||
const checkInterval = 1000; // Check every second
|
||||
|
||||
const checkServer = async () => {
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${port}/_health`);
|
||||
if (response.ok) {
|
||||
console.log('Strapi is ready, shutting down...');
|
||||
// In CI, we need to kill the entire process group
|
||||
if (process.env.CI) {
|
||||
process.kill(-strapiProcess.pid, 'SIGINT');
|
||||
} else {
|
||||
strapiProcess.kill('SIGINT');
|
||||
}
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
// Server not ready yet, continue checking
|
||||
}
|
||||
|
||||
if (Date.now() - startTime > timeout) {
|
||||
console.log('Timeout reached, forcing shutdown...');
|
||||
if (process.env.CI) {
|
||||
process.kill(-strapiProcess.pid, 'SIGKILL');
|
||||
} else {
|
||||
strapiProcess.kill('SIGKILL');
|
||||
}
|
||||
reject(new Error('Strapi failed to start within timeout period'));
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(checkServer, checkInterval);
|
||||
};
|
||||
|
||||
// Start checking
|
||||
checkServer();
|
||||
|
||||
// Log stdout and stderr for debugging
|
||||
strapiProcess.stdout.on('data', (data) => {
|
||||
console.log(`[stdout] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
strapiProcess.stderr.on('data', (data) => {
|
||||
console.error(`[stderr] ${data.toString().trim()}`);
|
||||
});
|
||||
|
||||
strapiProcess.on('error', (err) => {
|
||||
console.error(`[Strapi ERROR] Process error:`, err);
|
||||
reject(err);
|
||||
});
|
||||
|
||||
strapiProcess.on('exit', (code) => {
|
||||
console.log(`Strapi process exited with code ${code}`);
|
||||
});
|
||||
});
|
||||
|
||||
// Double check that Strapi has shut down
|
||||
await new Promise((resolve) => {
|
||||
const checkPort = async () => {
|
||||
try {
|
||||
await fetch(`http://127.0.0.1:${port}/_health`);
|
||||
// If we can connect, port is still in use
|
||||
setTimeout(checkPort, 1000);
|
||||
} catch (err) {
|
||||
// Port is free
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
checkPort();
|
||||
});
|
||||
|
||||
// Commit the generated files
|
||||
await execa('git', [...gitUser, 'add', '-A', '.'], {
|
||||
stdio: 'inherit',
|
||||
cwd: testAppPath,
|
||||
});
|
||||
|
||||
await execa('git', [...gitUser, 'commit', '-m', 'commit generated files'], {
|
||||
stdio: 'inherit',
|
||||
cwd: testAppPath,
|
||||
});
|
||||
|
||||
console.log(`Running ${chalk.blue(domain)} e2e tests`);
|
||||
|
||||
await execa(
|
||||
|
Loading…
x
Reference in New Issue
Block a user