diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index bf15d1e144..ca2ef65a76 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -195,7 +195,7 @@ jobs: uses: ./.github/actions/yarn-nm-install - name: Install Playwright Browsers - run: npx playwright@1.42.1 install --with-deps + run: npx playwright install --with-deps - name: Monorepo build uses: ./.github/actions/run-build @@ -209,7 +209,7 @@ jobs: - uses: actions/upload-artifact@v4 if: failure() with: - name: ce-${{ matrix.project }}--playwright-trace + name: ce-${{ matrix.project }}--playwright-trace-${{ github.run_id }}-${{ github.job }} path: test-apps/e2e/test-results/**/trace.zip retention-days: 1 @@ -239,7 +239,7 @@ jobs: uses: ./.github/actions/yarn-nm-install - name: Install Playwright Browsers - run: npx playwright@1.42.1 install --with-deps + run: npx playwright install --with-deps - name: Monorepo build uses: ./.github/actions/run-build @@ -254,7 +254,7 @@ jobs: - uses: actions/upload-artifact@v4 if: failure() with: - name: ee-${{ matrix.project }}--playwright-trace + name: ee-${{ matrix.project }}--playwright-trace-${{ github.run_id }}-${{ github.job }} path: test-apps/e2e/test-results/**/trace.zip retention-days: 1 diff --git a/package.json b/package.json index 523a479933..406e5bb817 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "@commitlint/cli": "19.2.0", "@commitlint/config-conventional": "19.1.0", "@commitlint/prompt-cli": "19.2.0", - "@playwright/test": "1.42.1", + "@playwright/test": "1.48.2", "@strapi/admin-test-utils": "workspace:*", "@strapi/eslint-config": "0.2.0", "@swc/cli": "0.3.10", diff --git a/tests/e2e/tests/content-releases/release-details-page.spec.ts b/tests/e2e/tests/content-releases/release-details-page.spec.ts index e7b9c1dd24..c889a6a928 100644 --- a/tests/e2e/tests/content-releases/release-details-page.spec.ts +++ b/tests/e2e/tests/content-releases/release-details-page.spec.ts @@ -1,5 +1,5 @@ import { test, expect, type Page } from '@playwright/test'; -import { describeOnCondition } from '../../utils/shared'; +import { clickAndWait, describeOnCondition } from '../../utils/shared'; import { resetDatabaseAndImportDataFromPath } from '../../utils/dts-import'; import { login } from '../../utils/login'; import { findAndClose } from '../../utils/shared'; @@ -56,9 +56,9 @@ describeOnCondition(edition === 'EE')('Release page', () => { await addEntryToRelease({ page, releaseName }); // Publish the release - await page.getByRole('link', { name: 'Releases' }).click(); - await page.getByRole('link', { name: `${releaseName}` }).click(); - await page.getByRole('button', { name: 'Publish' }).click(); + await clickAndWait(page, page.getByRole('link', { name: 'Releases' })); + await clickAndWait(page, page.getByRole('link', { name: `${releaseName}` })); + await clickAndWait(page, page.getByRole('button', { name: 'Publish', exact: true })); await expect(page.getByRole('heading', { name: releaseName })).toBeVisible(); // Check the already released release diff --git a/tests/e2e/tests/content-type-builder/components/update-components.spec.ts b/tests/e2e/tests/content-type-builder/components/update-components.spec.ts new file mode 100644 index 0000000000..812ac27f74 --- /dev/null +++ b/tests/e2e/tests/content-type-builder/components/update-components.spec.ts @@ -0,0 +1,154 @@ +import { test, expect } from '@playwright/test'; +import { resetFiles } from '../../../utils/file-reset'; +import { sharedSetup } from '../../../utils/setup'; +import { + createComponent, + createSingleType, + createCollectionType, + addAttributeToComponent, + removeAttributeFromComponent, + deleteComponent, + type AddAttribute, +} from '../../../utils/content-types'; +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 }); + + const originalAttributes = [{ type: 'text', name: 'testtext' }] satisfies AddAttribute[]; + + const addedAttribute = { + type: 'text', + name: 'addedtext', + }; + + const componentAttributeName = 'mycomponentname'; + + const singleType = { + attributes: [ + { + component: { + useExisting: 'SomeComponent', + options: { + name: componentAttributeName, + }, + }, + type: 'component', + name: componentAttributeName, + }, + ], + name: 'Singletypepage', + }; + + const collectionType = { + attributes: [ + { + component: { + useExisting: 'SomeComponent', + options: { + name: componentAttributeName, + }, + }, + type: 'component', + name: componentAttributeName, + }, + ], + name: 'Mycollectiontype', + }; + + test.beforeEach(async ({ page }) => { + await sharedSetup('update-component', page, { + resetFiles: true, + importData: 'with-admin.tar', + login: true, + skipTour: true, + afterSetup: async () => { + const options = { + name: 'SomeComponent', + categoryCreate: 'BlogPosts', + icon: 'paint', + attributes: originalAttributes, + }; + + await createComponent(page, options); + + // https://github.com/strapi/strapi/issues/21943 + // Until that's fixed we have to manually navigate away + await navToHeader(page, ['Content Manager', 'Homepage'], 'Homepage'); + await navToHeader(page, ['Content-Type Builder'], 'Article'); + + await createCollectionType(page, collectionType); + + await createSingleType(page, singleType); + }, + }); + }); + + test.afterAll(async () => { + await resetFiles(); + }); + + test('Add attribute to component', async ({ page }) => { + await addAttributeToComponent(page, 'SomeComponent', addedAttribute); + + // confirm that it exists in the content type this component was in + await navToHeader(page, ['Content-Type Builder', collectionType.name], collectionType.name); + await expect(page.getByText(addedAttribute.name, { exact: true })).toBeVisible(); + + // confirm that it exists in the single type this component was in + await navToHeader(page, ['Content-Type Builder', singleType.name], singleType.name); + await expect(page.getByText(addedAttribute.name, { exact: true })).toBeVisible(); + }); + + test('Remove attribute from component', async ({ page }) => { + const removedAttribute = originalAttributes[0]; + // confirm that it initially exists in the content type this component was in + await navToHeader(page, ['Content-Type Builder', collectionType.name], collectionType.name); + await expect(page.getByText(removedAttribute.name, { exact: true })).toBeVisible(); + + // confirm that it initially exists in the single type this component was in + await navToHeader(page, ['Content-Type Builder', singleType.name], singleType.name); + await expect(page.getByText(removedAttribute.name, { exact: true })).toBeVisible(); + + await removeAttributeFromComponent(page, 'SomeComponent', removedAttribute.name); + + // confirm that it no longer exists in the content type this component was in + await navToHeader(page, ['Content-Type Builder', collectionType.name], collectionType.name); + await expect(page.getByText(removedAttribute.name, { exact: true })).not.toBeVisible(); + + // confirm that it no longer exists in the single type this component was in + await navToHeader(page, ['Content-Type Builder', singleType.name], singleType.name); + await expect(page.getByText(removedAttribute.name, { exact: true })).not.toBeVisible(); + }); + + test('delete component', async ({ page }) => { + // confirm it exists in collection type + await navToHeader(page, ['Content-Type Builder', collectionType.name], collectionType.name); + await expect(page.getByText(componentAttributeName, { exact: true })).toBeVisible(); + + // confirm it exists in single type + await navToHeader(page, ['Content-Type Builder', singleType.name], singleType.name); + await expect(page.getByText(componentAttributeName, { exact: true })).toBeVisible(); + + // confirm it exists in navigation + await expect(page.getByRole('link', { name: 'SomeComponent' })).toBeVisible(); + + // delete it + await deleteComponent(page, 'SomeComponent'); + + // TODO: fix issue that components aren't removed from side navigation or content types until refresh + await page.reload({ waitUntil: 'networkidle' }); + + // confirm that it no longer exists in the content type this component was in + await navToHeader(page, ['Content-Type Builder', collectionType.name], collectionType.name); + await expect(page.getByText(componentAttributeName, { exact: true })).not.toBeVisible(); + + // confirm that it no longer exists in the single type this component was in + await navToHeader(page, ['Content-Type Builder', singleType.name], singleType.name); + await expect(page.getByText(componentAttributeName, { exact: true })).not.toBeVisible(); + + // confirm that is not longer exists in the navigation + await expect(page.getByRole('link', { name: 'SomeComponent' })).not.toBeVisible(); + }); +}); diff --git a/tests/e2e/utils/content-types.ts b/tests/e2e/utils/content-types.ts index c8baea5bd0..def4fdcf1d 100644 --- a/tests/e2e/utils/content-types.ts +++ b/tests/e2e/utils/content-types.ts @@ -373,3 +373,46 @@ export const createSingleType = async (page: Page, options: CreateContentTypeOpt export const createCollectionType = async (page: Page, options: CreateContentTypeOptions) => { await createContentType(page, options, 'collection'); }; + +export const addAttributeToComponent = async ( + page: Page, + componentName: string, + attribute: AddAttribute +) => { + await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' })); + await clickAndWait(page, page.getByRole('link', { name: componentName })); + await clickAndWait( + page, + page.getByRole('button', { name: 'Add another field to this component' }) + ); + await addAttributes(page, [attribute]); + + await saveAndVerifyContent(page, { + name: componentName, + attributes: [attribute], + }); +}; + +export const removeAttributeFromComponent = async ( + page: Page, + componentName: string, + attributeName: string +) => { + await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' })); + await clickAndWait(page, page.getByRole('link', { name: componentName })); + await clickAndWait(page, page.getByRole('button', { name: 'Delete ' + attributeName })); + + await saveAndVerifyContent(page, { name: componentName, attributes: [] }); +}; + +export const deleteComponent = async (page: Page, componentName: string) => { + await clickAndWait(page, page.getByRole('link', { name: 'Content-Type Builder' })); + await clickAndWait(page, page.getByRole('link', { name: componentName })); + await clickAndWait(page, page.getByRole('button', { name: 'Edit', exact: true })); + + // need to accept the browser modal + page.on('dialog', (dialog) => dialog.accept()); + await clickAndWait(page, page.getByRole('button', { name: 'Delete', exact: true })); + + await waitForRestart(page); +}; diff --git a/yarn.lock b/yarn.lock index c3c6f5f242..4d4e55ed0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4587,14 +4587,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.42.1": - version: 1.42.1 - resolution: "@playwright/test@npm:1.42.1" +"@playwright/test@npm:1.48.2": + version: 1.48.2 + resolution: "@playwright/test@npm:1.48.2" dependencies: - playwright: "npm:1.42.1" + playwright: "npm:1.48.2" bin: playwright: cli.js - checksum: 10c0/e5d7c1ffedabb934643edb010038edcb70d51d224fb6444844a854d94365a6179d4407a83da176cae37ccd42b62c148843e0b6f9b4c6506048e06558c00d4267 + checksum: 10c0/68bab3bee8d716111e9a166785e6c3c406b6a184fc46d03b5468fcbb92b6242e5628f6a75f9d286e2491ec0e9e59af67542a1f114b6659d790b5a1f41e4d305b languageName: node linkType: hard @@ -24435,27 +24435,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.42.1": - version: 1.42.1 - resolution: "playwright-core@npm:1.42.1" +"playwright-core@npm:1.48.2": + version: 1.48.2 + resolution: "playwright-core@npm:1.48.2" bin: playwright-core: cli.js - checksum: 10c0/9bb0be6defa32eb1b01429615f10c2ad17dcf701656c081a250369c1eb3b0dcc2a0ee21188cd653cdd2303ca73ff94df0d270b178fe3897eba274793dab368ce + checksum: 10c0/511da53d9df01fec5e5798915c68e7d1574890a504d1aae05430bf538d0080efa8db86e3dafdcd450f084ce7622f6bbede23ca52e798bfc4c3b3ea8da52a51f5 languageName: node linkType: hard -"playwright@npm:1.42.1": - version: 1.42.1 - resolution: "playwright@npm:1.42.1" +"playwright@npm:1.48.2": + version: 1.48.2 + resolution: "playwright@npm:1.48.2" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.42.1" + playwright-core: "npm:1.48.2" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10c0/91dcbfe92d75ca9eb4bfff69bb1ec28007b5a96f6187f48e52aa0f6acf8c24f6039ed6467c152964cc92f4ab64b85dc665b13c52b2fb9f7b9182ddb9db404e37 + checksum: 10c0/ecde4ee4767556868b24d7700f3502692a3cb14c8ef127052b51b48833ffcce80942954fb188a9b72505122b48b1b625d1bb486721e1c4f2e980215328ba1ad5 languageName: node linkType: hard @@ -27392,7 +27392,7 @@ __metadata: "@commitlint/cli": "npm:19.2.0" "@commitlint/config-conventional": "npm:19.1.0" "@commitlint/prompt-cli": "npm:19.2.0" - "@playwright/test": "npm:1.42.1" + "@playwright/test": "npm:1.48.2" "@strapi/admin-test-utils": "workspace:*" "@strapi/eslint-config": "npm:0.2.0" "@swc/cli": "npm:0.3.10"