diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6a4c18652..114849bb8b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -167,9 +167,14 @@ jobs: - name: Running E2E tests run: yarn test:e2e + env: + PRISMIC_URL: https://wroom.io + WROOM_EMAIL: ${{ secrets.EMAIL }} + WROOM_PASSWORD: ${{ secrets.PASSWORD }} - name: Upload HTML report uses: actions/upload-artifact@v3 + if: failure() with: name: Playwright HTML report path: playwright/playwright-report/ diff --git a/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js b/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js index 185d24d5c8..fc1308ca28 100644 --- a/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js +++ b/cypress/e2e/user-flows/scenario_custom_screenshots.cy.js @@ -26,7 +26,7 @@ describe("I am an existing SM user and I want to upload screenshots on variation sliceBuilder.goTo(slice.library, slice.name); }); - it("Upload and replace custom screenshots", () => { + it.skip("Upload and replace custom screenshots", () => { // Upload custom screenshot on default variation let sliceCard = new SliceCard(slice.name); sliceCard.imagePreview.should("not.exist"); diff --git a/e2e-projects/next-upgrade/package.json b/e2e-projects/next-upgrade/package.json index 7fce4b0ab0..df341a2ead 100644 --- a/e2e-projects/next-upgrade/package.json +++ b/e2e-projects/next-upgrade/package.json @@ -1,6 +1,6 @@ { "name": "next-upgrade", - "version": "1.21.2", + "version": "1.21.3-dev-next-release.3", "private": true, "scripts": { "dev": "next dev", diff --git a/e2e-projects/next/package.json b/e2e-projects/next/package.json index f7a5ee8ee8..06cb65079d 100644 --- a/e2e-projects/next/package.json +++ b/e2e-projects/next/package.json @@ -1,6 +1,6 @@ { "name": "cimsirp", - "version": "1.21.2", + "version": "1.21.3-dev-next-release.3", "private": true, "scripts": { "dev": "next dev", diff --git a/e2e-projects/sveltekit/package.json b/e2e-projects/sveltekit/package.json index fc1bbd1f9a..0f2be1183b 100644 --- a/e2e-projects/sveltekit/package.json +++ b/e2e-projects/sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "sveltekit", - "version": "1.21.2", + "version": "1.21.3-dev-next-release.3", "private": true, "scripts": { "dev": "vite dev", diff --git a/packages/adapter-next/package.json b/packages/adapter-next/package.json index 087c9422d9..553ff99602 100644 --- a/packages/adapter-next/package.json +++ b/packages/adapter-next/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/adapter-next", - "version": "0.3.28", + "version": "0.3.29-dev-next-release.3", "description": "Slice Machine adapter for Next.js.", "keywords": [ "typescript", diff --git a/packages/adapter-nuxt/package.json b/packages/adapter-nuxt/package.json index d52497bc1e..9b61a04984 100644 --- a/packages/adapter-nuxt/package.json +++ b/packages/adapter-nuxt/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/adapter-nuxt", - "version": "0.3.28", + "version": "0.3.29-dev-next-release.3", "description": "Slice Machine adapter for Nuxt 3.", "keywords": [ "typescript", diff --git a/packages/adapter-nuxt2/package.json b/packages/adapter-nuxt2/package.json index 54ce2b2ddb..0424ae1773 100644 --- a/packages/adapter-nuxt2/package.json +++ b/packages/adapter-nuxt2/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/adapter-nuxt2", - "version": "0.3.28", + "version": "0.3.29-dev-next-release.3", "description": "Slice Machine adapter for Nuxt 2.", "keywords": [ "typescript", diff --git a/packages/adapter-sveltekit/package.json b/packages/adapter-sveltekit/package.json index 5dbcee8d42..bc9516a14e 100644 --- a/packages/adapter-sveltekit/package.json +++ b/packages/adapter-sveltekit/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/adapter-sveltekit", - "version": "0.3.28", + "version": "0.3.29-dev-next-release.3", "description": "Slice Machine adapter for SvelteKit.", "keywords": [ "typescript", diff --git a/packages/init/package.json b/packages/init/package.json index 298c6f6c12..46f2c0dc2f 100644 --- a/packages/init/package.json +++ b/packages/init/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/init", - "version": "2.9.6", + "version": "2.9.7-dev-next-release.3", "description": "Init Prismic Slice Machine in your project", "keywords": [ "typescript", diff --git a/packages/manager/package.json b/packages/manager/package.json index b2a1c5826c..b988bd9957 100644 --- a/packages/manager/package.json +++ b/packages/manager/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/manager", - "version": "0.17.2", + "version": "0.17.3-dev-next-release.3", "description": "Manage all aspects of a Slice Machine project.", "repository": { "type": "git", @@ -65,7 +65,7 @@ }, "dependencies": { "@antfu/ni": "^0.20.0", - "@prismicio/custom-types-client": "1.2.0-alpha.0", + "@prismicio/custom-types-client": "^1.2.0", "@prismicio/mocks": "2.0.0", "@prismicio/types-internal": "^2.2.0", "@segment/analytics-node": "1.1.3", diff --git a/packages/manager/src/managers/SliceMachineManager.ts b/packages/manager/src/managers/SliceMachineManager.ts index 57590e116d..4c2e2d9196 100644 --- a/packages/manager/src/managers/SliceMachineManager.ts +++ b/packages/manager/src/managers/SliceMachineManager.ts @@ -3,10 +3,7 @@ import { CustomType, } from "@prismicio/types-internal/lib/customtypes"; import { SharedSliceContent } from "@prismicio/types-internal/lib/content"; -import { - ForbiddenError, - UnauthorizedError, -} from "@prismicio/custom-types-client"; +import * as prismicCustomTypesClient from "@prismicio/custom-types-client"; import { SliceMachinePlugin, SliceMachinePluginRunner, @@ -202,14 +199,16 @@ export class SliceMachineManager { authError, }; } catch (error) { - if (error instanceof ForbiddenError) { + if (error instanceof prismicCustomTypesClient.ForbiddenError) { authError = { name: "__stub__", message: "__stub__", reason: "__stub__", status: 401, }; - } else if (error instanceof UnauthorizedError) { + } else if ( + error instanceof prismicCustomTypesClient.UnauthorizedError + ) { authError = { name: "__stub__", message: "__stub__", diff --git a/packages/manager/src/managers/customTypes/CustomTypesManager.ts b/packages/manager/src/managers/customTypes/CustomTypesManager.ts index deab7e228a..a222cee4ec 100644 --- a/packages/manager/src/managers/customTypes/CustomTypesManager.ts +++ b/packages/manager/src/managers/customTypes/CustomTypesManager.ts @@ -235,8 +235,12 @@ export class CustomTypesManager extends BaseManager { endpoint: API_ENDPOINTS.PrismicModels, repositoryName, token: authenticationToken, - userAgent: args.userAgent || SLICE_MACHINE_USER_AGENT, fetch, + fetchOptions: { + headers: { + "User-Agent": args.userAgent || SLICE_MACHINE_USER_AGENT, + }, + }, }); try { @@ -323,8 +327,12 @@ export class CustomTypesManager extends BaseManager { endpoint: API_ENDPOINTS.PrismicModels, repositoryName, token: authenticationToken, - userAgent: SLICE_MACHINE_USER_AGENT, fetch, + fetchOptions: { + headers: { + "User-Agent": SLICE_MACHINE_USER_AGENT, + }, + }, }); return await client.getAllCustomTypes(); diff --git a/packages/manager/src/managers/slices/SlicesManager.ts b/packages/manager/src/managers/slices/SlicesManager.ts index 0735c988ba..47aca0cc81 100644 --- a/packages/manager/src/managers/slices/SlicesManager.ts +++ b/packages/manager/src/managers/slices/SlicesManager.ts @@ -771,8 +771,12 @@ export class SlicesManager extends BaseManager { endpoint: API_ENDPOINTS.PrismicModels, repositoryName, token: authenticationToken, - userAgent: args.userAgent || SLICE_MACHINE_USER_AGENT, fetch, + fetchOptions: { + headers: { + "User-Agent": args.userAgent || SLICE_MACHINE_USER_AGENT, + }, + }, }); try { @@ -987,8 +991,12 @@ export class SlicesManager extends BaseManager { endpoint: API_ENDPOINTS.PrismicModels, repositoryName, token: authenticationToken, - userAgent: SLICE_MACHINE_USER_AGENT, fetch, + fetchOptions: { + headers: { + "User-Agent": SLICE_MACHINE_USER_AGENT, + }, + }, }); return await client.getAllSharedSlices(); diff --git a/packages/manager/test/SliceMachineManager-customTypes-fetchRemoteCustomTypes.test.ts b/packages/manager/test/SliceMachineManager-customTypes-fetchRemoteCustomTypes.test.ts index 56455be3a8..e90d1c79c9 100644 --- a/packages/manager/test/SliceMachineManager-customTypes-fetchRemoteCustomTypes.test.ts +++ b/packages/manager/test/SliceMachineManager-customTypes-fetchRemoteCustomTypes.test.ts @@ -38,7 +38,8 @@ it("fetches Custom Types from the Custom Types API", async (ctx) => { onCustomTypeGetAll: (req, res, ctx) => { if ( req.headers.get("repository") === sliceMachineConfig.repositoryName && - req.headers.get("Authorization") === `Bearer ${authenticationToken}` + req.headers.get("Authorization") === `Bearer ${authenticationToken}` && + req.headers.get("user-agent") === "slice-machine" ) { return res(ctx.json(models)); } @@ -84,7 +85,8 @@ it("fetches Custom Types from the Custom Types API using the currently set envir onCustomTypeGetAll: (req, res, ctx) => { if ( req.headers.get("repository") === "foo" && - req.headers.get("Authorization") === `Bearer ${authenticationToken}` + req.headers.get("Authorization") === `Bearer ${authenticationToken}` && + req.headers.get("user-agent") === "slice-machine" ) { return res(ctx.json(models)); } diff --git a/packages/manager/test/SliceMachineManager-customTypes-pushCustomType.test.ts b/packages/manager/test/SliceMachineManager-customTypes-pushCustomType.test.ts index a1d9647737..5063da52c6 100644 --- a/packages/manager/test/SliceMachineManager-customTypes-pushCustomType.test.ts +++ b/packages/manager/test/SliceMachineManager-customTypes-pushCustomType.test.ts @@ -137,6 +137,56 @@ it("uses the update endpoint if the Custom Type already exists", async (ctx) => expect(sentModel).toStrictEqual({ ...model, format: "custom" }); }); +it("sends the provided user agent", async (ctx) => { + const model = ctx.mockPrismic.model.customType(); + const adapter = createTestPlugin({ + setup: ({ hook }) => { + hook("custom-type:read", () => { + return { model }; + }); + }, + }); + const cwd = await createTestProject({ adapter }); + const manager = createSliceMachineManager({ + nativePlugins: { [adapter.meta.name]: adapter }, + cwd, + }); + + await manager.plugins.initPlugins(); + + const repositoryName = await manager.project.getRepositoryName(); + + let sentModel; + + mockPrismicUserAPI(ctx); + mockPrismicAuthAPI(ctx); + mockCustomTypesAPI(ctx, { + async onCustomTypeGet(req, res, ctx) { + if ( + req.headers.get("user-agent") === "foo" && + req.headers.get("repository") === repositoryName + ) { + return res(ctx.status(404)); + } + }, + async onCustomTypeInsert(req, res, ctx) { + if ( + req.headers.get("user-agent") === "foo" && + req.headers.get("repository") === repositoryName + ) { + sentModel = await req.json(); + + return res(ctx.status(201)); + } + }, + }); + + await manager.user.login(createPrismicAuthLoginResponse()); + await manager.customTypes.pushCustomType({ id: model.id, userAgent: "foo" }); + // TODO: update prismicio/mock library + expect(sentModel).toStrictEqual({ ...model, format: "custom" }); +}); + it("throws if plugins have not been initialized", async () => { const cwd = await createTestProject(); const manager = createSliceMachineManager({ cwd }); diff --git a/packages/manager/test/SliceMachineManager-prismicRepository-pushChanges.test.ts b/packages/manager/test/SliceMachineManager-prismicRepository-pushChanges.test.ts index 70758fc5c2..7ef3c88b47 100644 --- a/packages/manager/test/SliceMachineManager-prismicRepository-pushChanges.test.ts +++ b/packages/manager/test/SliceMachineManager-prismicRepository-pushChanges.test.ts @@ -31,7 +31,7 @@ const pushChangesPayload = ( ], }); -it("pushes changes using the bulk delete API", async (ctx) => { +it("pushes changes using the bulk API", async (ctx) => { const customTypeModel = ctx.mockPrismic.model.customType(); const sharedSliceModel = ctx.mockPrismic.model.sharedSlice(); const adapter = createTestPlugin({ @@ -58,9 +58,11 @@ it("pushes changes using the bulk delete API", async (ctx) => { mockPrismicAuthAPI(ctx); mockCustomTypesAPI(ctx, { async onBulk(req, res, ctx) { - sentModel = await req.json(); + if (req.headers.get("user-agent") === "slice-machine") { + sentModel = await req.json(); - return res(ctx.status(204)); + return res(ctx.status(204)); + } }, }); @@ -100,7 +102,7 @@ it("pushes changes using the bulk delete API", async (ctx) => { expect(sentModel).toStrictEqual(expectedAPIPayload); }); -it("pushes changes using the bulk delete API to the production repository when an environment is set", async (ctx) => { +it("pushes changes using the bulk API to the selected environment when an environment is set", async (ctx) => { const customTypeModel = ctx.mockPrismic.model.customType(); const sharedSliceModel = ctx.mockPrismic.model.sharedSlice(); const adapter = createTestPlugin({ @@ -129,7 +131,10 @@ it("pushes changes using the bulk delete API to the production repository when a mockPrismicAuthAPI(ctx); mockCustomTypesAPI(ctx, { async onBulk(req, res, ctx) { - if (req.headers.get("repository") === "foo") { + if ( + req.headers.get("user-agent") === "slice-machine" && + req.headers.get("repository") === "foo" + ) { sentModel = await req.json(); return res(ctx.status(204)); diff --git a/packages/manager/test/SliceMachineManager-slices-fetchRemoteSlices.test.ts b/packages/manager/test/SliceMachineManager-slices-fetchRemoteSlices.test.ts index 271744671a..42e8b37a69 100644 --- a/packages/manager/test/SliceMachineManager-slices-fetchRemoteSlices.test.ts +++ b/packages/manager/test/SliceMachineManager-slices-fetchRemoteSlices.test.ts @@ -38,7 +38,8 @@ it("fetches Slices from the Custom Types API", async (ctx) => { onSliceGetAll: (req, res, ctx) => { if ( req.headers.get("repository") === sliceMachineConfig.repositoryName && - req.headers.get("Authorization") === `Bearer ${authenticationToken}` + req.headers.get("Authorization") === `Bearer ${authenticationToken}` && + req.headers.get("user-agent") === "slice-machine" ) { return res(ctx.json(models)); } @@ -84,7 +85,8 @@ it("fetches Slices from the Custom Types API using the currently set environment onSliceGetAll: (req, res, ctx) => { if ( req.headers.get("repository") === "foo" && - req.headers.get("Authorization") === `Bearer ${authenticationToken}` + req.headers.get("Authorization") === `Bearer ${authenticationToken}` && + req.headers.get("user-agent") === "slice-machine" ) { return res(ctx.json(models)); } diff --git a/packages/manager/test/SliceMachineManager-slices-pushSlice.test.ts b/packages/manager/test/SliceMachineManager-slices-pushSlice.test.ts index 7ec28a14b3..c3ec53dc96 100644 --- a/packages/manager/test/SliceMachineManager-slices-pushSlice.test.ts +++ b/packages/manager/test/SliceMachineManager-slices-pushSlice.test.ts @@ -472,6 +472,84 @@ it("returns a record of variation IDs mapped to uploaded screenshot URLs", async }); }); +it("sends the provided user agent", async (ctx) => { + const model = ctx.mockPrismic.model.sharedSlice({ + variations: [ctx.mockPrismic.model.sharedSliceVariation()], + }); + const sliceReadHookHandler = vi.fn(() => { + return { model }; + }); + const adapter = createTestPlugin({ + setup: ({ hook }) => { + hook("slice:read", sliceReadHookHandler); + }, + }); + const cwd = await createTestProject({ adapter }); + const manager = createSliceMachineManager({ + nativePlugins: { [adapter.meta.name]: adapter }, + cwd, + }); + + await manager.plugins.initPlugins(); + + mockPrismicUserAPI(ctx); + mockPrismicAuthAPI(ctx); + + await manager.user.login(createPrismicAuthLoginResponse()); + + const authenticationToken = await manager.user.getAuthenticationToken(); + const repositoryName = await manager.project.getRepositoryName(); + + let sentModel; + + mockCustomTypesAPI(ctx, { + async onSliceGet(req, res, ctx) { + if ( + req.headers.get("user-agent") === "foo" && + req.headers.get("repository") === repositoryName + ) { + return res(ctx.status(404)); + } + }, + async onSliceInsert(req, res, ctx) { + if ( + req.headers.get("user-agent") === "foo" && + req.headers.get("repository") === repositoryName + ) { + sentModel = await req.json(); + + return res(ctx.status(201)); + } + }, + }); + mockAWSACLAPI(ctx, { + createEndpoint: { + expectedPrismicRepository: repositoryName, + expectedAuthenticationToken: authenticationToken, + }, + }); + + await manager.slices.pushSlice({ + libraryID: "foo", + sliceID: model.id, + userAgent: "foo", + }); + + expectHookHandlerToHaveBeenCalledWithData(sliceReadHookHandler, { + libraryID: "foo", + sliceID: model.id, + }); + expect(sentModel).toStrictEqual({ + ...model, + variations: model.variations.map((variation) => { + return { + ...variation, + imageUrl: expect.any(String), + }; + }), + }); +}); + it("throws if plugins have not been initialized", async () => { const cwd = await createTestProject(); const manager = createSliceMachineManager({ cwd }); diff --git a/packages/plugin-kit/package.json b/packages/plugin-kit/package.json index a5388ba2f3..e485a40f12 100644 --- a/packages/plugin-kit/package.json +++ b/packages/plugin-kit/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/plugin-kit", - "version": "0.4.28", + "version": "0.4.29-dev-next-release.3", "description": "A set of helpers to develop and run Slice Machine plugins", "keywords": [ "typescript", diff --git a/packages/slice-machine/components/CustomTypeTable/changesPage.tsx b/packages/slice-machine/components/CustomTypeTable/changesPage.tsx index 596d2543c9..c0d17609a1 100644 --- a/packages/slice-machine/components/CustomTypeTable/changesPage.tsx +++ b/packages/slice-machine/components/CustomTypeTable/changesPage.tsx @@ -74,7 +74,7 @@ export const CustomTypeTable: React.FC = ({ key={customType.local.id} legacyBehavior > - + = ({ ) : ( - + = ({ loading: sliceBuilderState.loading, }} cancel={{ text: "Cancel" }} + size="medium" /> diff --git a/packages/slice-machine/components/Forms/RenameVariationModal/RenameVariationModal.tsx b/packages/slice-machine/components/Forms/RenameVariationModal/RenameVariationModal.tsx index 73ad0db15d..3eb2d454bf 100644 --- a/packages/slice-machine/components/Forms/RenameVariationModal/RenameVariationModal.tsx +++ b/packages/slice-machine/components/Forms/RenameVariationModal/RenameVariationModal.tsx @@ -106,6 +106,7 @@ export const RenameVariationModal: FC = ({ disabled: !formik.isValid, }} cancel={{ text: "Cancel" }} + size="medium" /> )} diff --git a/packages/slice-machine/lib/builders/SliceBuilder/Sidebar/index.tsx b/packages/slice-machine/lib/builders/SliceBuilder/Sidebar/index.tsx index bf960271bc..b57ce844e5 100644 --- a/packages/slice-machine/lib/builders/SliceBuilder/Sidebar/index.tsx +++ b/packages/slice-machine/lib/builders/SliceBuilder/Sidebar/index.tsx @@ -1,4 +1,4 @@ -import { Box, Button } from "@prismicio/editor-ui"; +import { Box, Button, Gradient } from "@prismicio/editor-ui"; import { useRouter } from "next/router"; import { type Dispatch, type FC, type SetStateAction, useState } from "react"; @@ -73,16 +73,32 @@ export const Sidebar: FC = (props) => { variationId={v.id} /> ))} - + + + = ( /> )} - + {variant === "default" || variant === "unauthenticated" ? ( Environment @@ -57,9 +64,7 @@ export const SideNavEnvironmentSelector: FC = ( ) : undefined} {variant === "unauthenticated" ? ( - - Login required - + ) : undefined} {variant === "default" ? ( @@ -72,7 +77,10 @@ export const SideNavEnvironmentSelector: FC = ( {variant === "unauthenticated" ? ( - + } + onClick={onLogInClick} + /> ) : undefined} {environments.length > 1 ? ( diff --git a/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx b/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx index b1a88084bc..057fa0e6cd 100644 --- a/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx +++ b/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewSliceDialog.tsx @@ -141,6 +141,7 @@ export const ConvertLegacySliceAsNewSliceDialog: FC = ({ disabled: !formik.isValid, }} cancel={{ text: "Cancel" }} + size="medium" /> diff --git a/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx b/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx index d75f302a22..f89e0afef3 100644 --- a/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx +++ b/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceAsNewVariationDialog.tsx @@ -178,6 +178,7 @@ export const ConvertLegacySliceAsNewVariationDialog: FC = ({ disabled: !formik.isValid, }} cancel={{ text: "Cancel" }} + size="medium" /> diff --git a/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx b/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx index cf78c80f4c..b0c9f75386 100644 --- a/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx +++ b/packages/slice-machine/src/features/slices/convertLegacySlice/ConvertLegacySliceMergeWithIdenticalDialog.tsx @@ -97,6 +97,7 @@ export const ConvertLegacySliceMergeWithIdenticalDialog: FC = ({ disabled: !formik.isValid, }} cancel={{ text: "Cancel" }} + size="medium" /> diff --git a/packages/slice-machine/src/icons/LoginIcon.tsx b/packages/slice-machine/src/icons/LoginIcon.tsx new file mode 100644 index 0000000000..c3decb9067 --- /dev/null +++ b/packages/slice-machine/src/icons/LoginIcon.tsx @@ -0,0 +1,13 @@ +import type { FC, SVGProps } from "react"; + +export const LoginIcon: FC> = (props) => ( + + + + + + +); diff --git a/packages/start-slicemachine/package.json b/packages/start-slicemachine/package.json index ae651775cf..6db03a3c62 100644 --- a/packages/start-slicemachine/package.json +++ b/packages/start-slicemachine/package.json @@ -1,6 +1,6 @@ { "name": "start-slicemachine", - "version": "0.12.8", + "version": "0.12.9-dev-next-release.3", "description": "Start Slice Machine from within a project.", "repository": { "type": "git", diff --git a/playwright/.env.local.example b/playwright/.env.local.example new file mode 100644 index 0000000000..d7e8b75711 --- /dev/null +++ b/playwright/.env.local.example @@ -0,0 +1,5 @@ +WROOM_EMAIL=email@example.com +WROOM_PASSWORD=my_password + +PRISMIC_EMAIL=email@example.com +PRISMIC_PASSWORD=my_password diff --git a/playwright/.gitignore b/playwright/.gitignore index 1528260fc7..3b98d49bbe 100644 --- a/playwright/.gitignore +++ b/playwright/.gitignore @@ -1,3 +1,3 @@ +.env.local /test-results/ /playwright-report/ - diff --git a/playwright/README.md b/playwright/README.md index 6577c1ba1c..29ab575e02 100644 --- a/playwright/README.md +++ b/playwright/README.md @@ -10,6 +10,17 @@ _Install browsers and OS dependencies for Playwright._ yarn test:e2e:install ``` +- Create a `.env.local` file + +Copy-paste `playwright/.env.local.example` in `playwright/.env.local` and update `EMAIL` and `PASSWORD` values. + +Having both Wroom and Prismic values will help you run Slice Machine in dev or prod mode without having to take care of the correct email or password. +Wroom or Prismic values will be used depending on the Prismic URL. + +- Install the VS Code extension (optional) + +Playwright Test extension was created specifically to accommodate the needs of e2e testing. [Install Playwright Test for VSCode by reading this page](https://playwright.dev/docs/getting-started-vscode). It will help you to debug a problem in tests if needed. + ## How to run tests - Run all tests @@ -63,6 +74,59 @@ To open a downloaded CI test report from anywhere in your computer: npx playwright show-report name-of-my-extracted-playwright-report ``` +## Creating tests + +### `test.run()` + +Use `test.run()` to create a test. `run` function take an optional object parameter `options` that let you configure how you want to run the test. +You can configure if you want a logged in test and also an onboarded test. +Default is not logged in and onboarded. + +Example for a logged in user not onboarded: + +```ts +test.run({ loggedIn: true, onboarded: false })("I can ...", + async ({ sliceBuilderPage, slicesListPage }) => { + // Test content + }); +``` + +Warning: Only use `loggedIn: true` when it's necessary for your test, it increases local test time (not in CI) by some seconds (≃ 3 secs). + +### Mocking with `mockManagerProcedures` + +Use `mockManagerProcedures` function when you need to mock a manager procedure response. +With the way playwright intercept requests you need to give an array of procedures to mock. +You will have the possibility to return fake data on top of the existing one. +If you don't need to return data and / or let the manager execute the procedure at all, you can disable it with `execute` property, it will return an empty object to the UI. + +Example: +```ts +await mockManagerProcedures({ + page: changesPage.page, + procedures: [ + { + path: "getState", + data: (data) => ({ + ...data, + libraries: emptyLibraries, + customTypes: [], + remoteCustomTypes: [], + remoteSlices: [], + }), + }, + { + path: "prismicRepository.pushChanges", + execute: false, + }, + ], +}); +``` + +Warning: Only mock when it's necessary because the state of Slice Machine or the remote repository can change. +We want to ensure test can be launched on any state of Slice Machine and any state of repository. Mocking will help you do that. +In theory, we want to avoid mocking while doing e2e tests. Smoke tests don't have any mocking but standalone tests can when it's necessary. It improves the DX and reduce the necessary setup that we can have for Smoke tests. + ## Best practices 1. Always use the "Page Object Model" for Locators @@ -111,7 +175,20 @@ this.appVersion = this.menu.getByTestId("slicemachine-version"); Our e2e tests should not break whatever the current state of Slice Machine is. Having existing data or not, staging or production, etc. -5. Write your own best practice for the team here... +5. Always create tests from the user perspective + +When creating tests it's important to test what users can see and do. +Start the test name with "I" so you prevent yourself testing implementation details. + +Example: + +```ts +test.run()("I can create a slice", async () => { + // Test content +}); +``` + +6. Write your own best practice for the team here... ## Useful links diff --git a/playwright/fixtures/index.ts b/playwright/fixtures/index.ts index 02e44cf530..32d1ac75f5 100644 --- a/playwright/fixtures/index.ts +++ b/playwright/fixtures/index.ts @@ -1,3 +1,8 @@ +import * as dotenv from "dotenv"; +import * as fs from "fs/promises"; +import * as os from "os"; +import * as path from "path"; +import { decode } from "@msgpack/msgpack"; import { test as baseTest, expect } from "@playwright/test"; import { PageTypesTablePage } from "../pages/PageTypesTablePage"; @@ -8,13 +13,17 @@ import { SlicesListPage } from "../pages/SlicesListPage"; import { SliceBuilderPage } from "../pages/SliceBuilderPage"; import { ChangesPage } from "../pages/ChangesPage"; import { ChangelogPage } from "../pages/ChangelogPage"; -import config from "../playwright.config"; +import { SliceMachinePage } from "../pages/SliceMachinePage"; import { generateRandomId } from "../utils/generateRandomId"; +import config from "../playwright.config"; -export type Fixtures = { +dotenv.config({ path: `.env.local` }); + +export type DefaultFixtures = { /** * Pages */ + sliceMachinePage: SliceMachinePage; pageTypesTablePage: PageTypesTablePage; pageTypesBuilderPage: PageTypeBuilderPage; customTypesTablePage: CustomTypesTablePage; @@ -35,113 +44,242 @@ export type Fixtures = { /** * Default test fixture */ -export const test = baseTest.extend({ - /** - * Pages - */ - pageTypesTablePage: async ({ page }, use) => { - await use(new PageTypesTablePage(page)); - }, - pageTypesBuilderPage: async ({ page }, use) => { - await use(new PageTypeBuilderPage(page)); - }, - customTypesTablePage: async ({ page }, use) => { - await use(new CustomTypesTablePage(page)); - }, - customTypesBuilderPage: async ({ page }, use) => { - await use(new CustomTypesBuilderPage(page)); - }, - slicesListPage: async ({ page }, use) => { - await use(new SlicesListPage(page)); - }, - sliceBuilderPage: async ({ page }, use) => { - await use(new SliceBuilderPage(page)); - }, - changesPage: async ({ page }, use) => { - await use(new ChangesPage(page)); - }, - changelogPage: async ({ page }, use) => { - await use(new ChangelogPage(page)); - }, +export const defaultTest = ( + options: { loggedIn?: boolean; onboarded?: boolean } = {}, +) => { + const { loggedIn = false, onboarded = true } = options; - /** - * Data - */ - pageType: async ({ pageTypesTablePage }, use) => { - await pageTypesTablePage.goto(); - await pageTypesTablePage.openCreateDialog(); + return baseTest.extend({ + /** + * Pages + */ + sliceMachinePage: async ({ page }, use) => { + await use(new SliceMachinePage(page)); + }, + pageTypesTablePage: async ({ page }, use) => { + await use(new PageTypesTablePage(page)); + }, + pageTypesBuilderPage: async ({ page }, use) => { + await use(new PageTypeBuilderPage(page)); + }, + customTypesTablePage: async ({ page }, use) => { + await use(new CustomTypesTablePage(page)); + }, + customTypesBuilderPage: async ({ page }, use) => { + await use(new CustomTypesBuilderPage(page)); + }, + slicesListPage: async ({ page }, use) => { + await use(new SlicesListPage(page)); + }, + sliceBuilderPage: async ({ page }, use) => { + await use(new SliceBuilderPage(page)); + }, + changesPage: async ({ page }, use) => { + await use(new ChangesPage(page)); + }, + changelogPage: async ({ page }, use) => { + await use(new ChangelogPage(page)); + }, - const pageTypeName = "Page Type " + generateRandomId(); - await pageTypesTablePage.createTypeDialog.createType(pageTypeName); + /** + * Data + */ + pageType: async ({ pageTypesTablePage }, use) => { + await pageTypesTablePage.goto(); + await pageTypesTablePage.openCreateDialog(); - await use({ name: pageTypeName }); - }, - customType: async ({ customTypesTablePage }, use) => { - await customTypesTablePage.goto(); - await customTypesTablePage.openCreateDialog(); + const pageTypeName = "Page Type " + generateRandomId(); + await pageTypesTablePage.createTypeDialog.createType(pageTypeName); - const customTypeName = "Custom Type " + generateRandomId(); - await customTypesTablePage.createTypeDialog.createType(customTypeName); + await use({ name: pageTypeName }); + }, + customType: async ({ customTypesTablePage }, use) => { + await customTypesTablePage.goto(); + await customTypesTablePage.openCreateDialog(); - await use({ name: customTypeName }); - }, - slice: async ({ slicesListPage }, use) => { - await slicesListPage.goto(); - await expect(slicesListPage.breadcrumbLabel).toBeVisible(); - await slicesListPage.openCreateDialog(); + const customTypeName = "Custom Type " + generateRandomId(); + await customTypesTablePage.createTypeDialog.createType(customTypeName); - const sliceName = "Slice" + generateRandomId(); - await slicesListPage.createSliceDialog.createSlice(sliceName); + await use({ name: customTypeName }); + }, + slice: async ({ slicesListPage }, use) => { + await slicesListPage.goto(); + await expect(slicesListPage.breadcrumbLabel).toBeVisible(); + await slicesListPage.openCreateDialog(); - await use({ name: sliceName }); - }, + const sliceName = "Slice" + generateRandomId(); + await slicesListPage.createSliceDialog.createSlice(sliceName); - /** - * Page object override - */ - page: async ({ browser }, use) => { - // Onboard user in Local Storage - const context = await browser.newContext({ - storageState: { - cookies: [], - origins: [ - { - origin: config.use.baseURL, - localStorage: [ - { - name: "persist:root", - value: JSON.stringify({ - userContext: { - userReview: { - onboarding: true, - advancedRepository: true, + await use({ name: sliceName }); + }, + + /** + * Page + */ + page: async ({ browser }, use) => { + // Onboard user in Local Storage by default + let context = await browser.newContext({ + storageState: { + cookies: [], + origins: [ + { + origin: config.use.baseURL, + localStorage: [ + { + name: "persist:root", + value: JSON.stringify({ + userContext: { + userReview: { + onboarding: true, + advancedRepository: true, + }, + updatesViewed: { + latest: null, + latestNonBreaking: null, + }, + hasSeenChangesToolTip: true, + hasSeenSimulatorToolTip: true, + hasSeenTutorialsToolTip: true, + authStatus: "unknown", + lastSyncChange: null, }, - updatesViewed: { latest: null, latestNonBreaking: null }, - hasSeenChangesToolTip: true, - hasSeenSimulatorToolTip: true, - hasSeenTutorialsToolTip: true, - authStatus: "unknown", - lastSyncChange: null, - }, - }), - }, + }), + }, + { + name: "slice-machine_isInAppGuideOpen", + value: "false", + }, + ], + }, + ], + }, + }); + + // Prevent user to be onboarded if needed + if (!onboarded) { + context = await browser.newContext({ + storageState: { + cookies: [], + origins: [ { - name: "slice-machine_isInAppGuideOpen", - value: "false", + origin: config.use.baseURL, + localStorage: [], }, ], }, - ], - }, - }); + }); + } + + // Create new page object with new context + const page = await context.newPage(); + + // Logout user by default + try { + await fs.rm(path.join(os.homedir(), ".prismic")); + } catch (error) { + // Ignore since it means the user is already logged out + } + + // Login user if needed + if (loggedIn) { + // In CI we define a PRISMIC_URL env variable to fasten the tests + let prismicUrl = process.env["PRISMIC_URL"]; + + // In local we get the Prismic URL from the browser, it helps to avoid + // switching manually between Wroom and Prismic + if (!prismicUrl) { + await page.route( + "*/**/_manager", + async (route) => { + const postDataBuffer = route.request().postDataBuffer() as Buffer; + const postData = decode(postDataBuffer) as Record< + "procedurePath", + unknown[] + >; + + if (postData.procedurePath[0] === "getState") { + const response = await route.fetch(); + const existingBody = await response.body(); + const existingData = ( + decode(existingBody) as Record<"data", unknown> + ).data as Record< + "env", + { + endpoints: { PrismicWroom: string }; + } + >; + + // Get Prismic URL from the response of the getState call + prismicUrl = existingData.env.endpoints.PrismicWroom; - const page = await context.newPage(); + await route.continue(); + } + }, + // Ensure only the first getState call is intercepted + { + times: 1, + }, + ); - // Use the fixture value in the test. - await use(page); + // Visit the page to trigger the getState call and wait for it + await page.goto("/"); + await page.waitForResponse("*/**/_manager"); + } - // Gracefully close up everything - await page.close(); - await context.close(); - }, -}); + const activeEnv = prismicUrl?.includes("wroom") ? "WROOM" : "PRISMIC"; + const email = process.env[`${activeEnv}_EMAIL`]; + const password = process.env[`${activeEnv}_PASSWORD`]; + + if (!prismicUrl) { + console.warn("Could not find Prismic URL."); + } else if (!email || !password) { + console.warn( + `Missing EMAIL or PASSWORD environment variables for ${activeEnv} environment.`, + ); + } else { + // Do the authentication call + const res = await fetch( + new URL("./authentication/signin", prismicUrl).toString(), + { + method: "post", + body: JSON.stringify({ + email, + password, + }), + headers: { + "Content-Type": "application/json", + }, + }, + ); + + if (!res.headers.has("Set-Cookie")) { + // If the authentication fails, log the error + const reason = await res.text(); + console.error( + "Could not authenticate to prismic. Please check the credentials.", + reason, + ); + } else { + // If the authentication succeeded, save the cookies to persist it + await fs.writeFile( + path.join(os.homedir(), ".prismic"), + JSON.stringify({ + base: new URL(prismicUrl).toString(), + cookies: + res.headers.get("Set-Cookie")?.split(", ").join("; ") ?? "", + }), + ); + } + } + } + + // Propagate the modified page to the test + await use(page); + }, + }); +}; + +export const test = { + ...baseTest, + run: defaultTest, +}; diff --git a/playwright/mocks/emptyLibraries.ts b/playwright/mocks/emptyLibraries.ts new file mode 100644 index 0000000000..900f370f4c --- /dev/null +++ b/playwright/mocks/emptyLibraries.ts @@ -0,0 +1,13 @@ +export const emptyLibraries = [ + { + name: "slices", + path: "slices", + isLocal: true, + components: [], + meta: { + isNodeModule: false, + isDownloaded: false, + isManual: true, + }, + }, +]; diff --git a/playwright/mocks/index.ts b/playwright/mocks/index.ts new file mode 100644 index 0000000000..63026fc81c --- /dev/null +++ b/playwright/mocks/index.ts @@ -0,0 +1,2 @@ +export { emptyLibraries } from "./emptyLibraries"; +export { simpleCustomType } from "./simpleCustomType"; diff --git a/playwright/mocks/simpleCustomType.ts b/playwright/mocks/simpleCustomType.ts new file mode 100644 index 0000000000..41be73139b --- /dev/null +++ b/playwright/mocks/simpleCustomType.ts @@ -0,0 +1,15 @@ +export const simpleCustomType = { + id: "footer", + label: "Footer", + repeatable: false, + status: true, + json: { + Main: { + title: { + type: "Text", + config: { label: "Title", placeholder: "" }, + }, + }, + }, + format: "custom", +}; diff --git a/playwright/package.json b/playwright/package.json index e98bc5675b..87ffb6b739 100644 --- a/playwright/package.json +++ b/playwright/package.json @@ -1,6 +1,6 @@ { "name": "@slicemachine/e2e", - "version": "0.0.1", + "version": "0.0.2-dev-next-release.3", "private": true, "description": "E2E tests for Slice Machine", "author": "Prismic (https://prismic.io)", @@ -15,10 +15,12 @@ "audit": "echo \"No audit for playwright\" && true" }, "devDependencies": { + "@msgpack/msgpack": "2.8.0", "@playwright/test": "1.39.0", "@typescript-eslint/eslint-plugin": "5.55.0", "@typescript-eslint/parser": "5.55.0", "depcheck": "1.4.3", + "dotenv": "16.3.1", "eslint": "8.37.0", "eslint-config-prettier": "9.0.0", "typescript": "4.9.5" diff --git a/playwright/pages/ChangelogPage.ts b/playwright/pages/ChangelogPage.ts index 38851333a1..c6736ae111 100644 --- a/playwright/pages/ChangelogPage.ts +++ b/playwright/pages/ChangelogPage.ts @@ -1,6 +1,6 @@ import { Locator, Page } from "@playwright/test"; -import { SliceMachinePage } from "./components/SliceMachinePage"; +import { SliceMachinePage } from "./SliceMachinePage"; export class ChangelogPage extends SliceMachinePage { readonly breadcrumbLabel: Locator; diff --git a/playwright/pages/ChangesPage.ts b/playwright/pages/ChangesPage.ts index 29594a4dbe..8486a6eb49 100644 --- a/playwright/pages/ChangesPage.ts +++ b/playwright/pages/ChangesPage.ts @@ -1,12 +1,17 @@ import { Locator, Page, expect } from "@playwright/test"; import { LoginDialog } from "./components/LoginDialog"; -import { SliceMachinePage } from "./components/SliceMachinePage"; +import { SliceMachinePage } from "./SliceMachinePage"; export class ChangesPage extends SliceMachinePage { readonly loginDialog: LoginDialog; - readonly breadcrumbLabel: Locator; + readonly loginButton: Locator; + readonly pushChangesButton: Locator; + readonly pushedMessaged: Locator; + readonly notLoggedInTitle: Locator; + readonly notAuthorizedTitle: Locator; + readonly blankSlateTitle: Locator; constructor(page: Page) { super(page); @@ -20,23 +25,57 @@ export class ChangesPage extends SliceMachinePage { * Static locators */ this.breadcrumbLabel = this.breadcrumb.getByText("Changes"); + this.loginButton = this.body.getByText("Log in to Prismic"); + this.pushChangesButton = page.getByText("Push Changes"); + this.pushedMessaged = page.getByText( + "All slices and types have been pushed", + ); + this.notLoggedInTitle = page.getByText("It seems like you are logged out"); + this.notAuthorizedTitle = page.getByText( + "It seems like you don't have access to this repository", + ); + this.blankSlateTitle = page.getByText("Everything up-to-date"); } /** * Dynamic locators */ - // Handle dynamic locators here + getCustomType(id: string) { + return this.page.getByTestId(`custom-type-${id}`); + } /** * Actions */ async goto() { await this.page.goto("/changes"); - await expect(this.breadcrumb).toBeVisible(); + await expect(this.breadcrumbLabel).toBeVisible(); + } + + async pushChanges() { + await this.pushChangesButton.click(); + await expect(this.pushChangesButton).toBeDisabled(); + await expect(this.pushedMessaged).toBeVisible(); } /** * Assertions */ - // Handle assertions here + async checkCustomTypeName(id: string, name: string) { + await expect( + this.getCustomType(id).getByText(name, { exact: true }), + ).toBeVisible(); + } + + async checkCustomTypeApiId(id: string) { + await expect( + this.getCustomType(id).getByText(id, { exact: true }), + ).toBeVisible(); + } + + async checkCustomTypeStatus(id: string, status: string) { + await expect( + this.getCustomType(id).getByText(status, { exact: true }), + ).toBeVisible(); + } } diff --git a/playwright/pages/components/SliceMachinePage.ts b/playwright/pages/SliceMachinePage.ts similarity index 68% rename from playwright/pages/components/SliceMachinePage.ts rename to playwright/pages/SliceMachinePage.ts index 5f94805b08..6cbc041de6 100644 --- a/playwright/pages/components/SliceMachinePage.ts +++ b/playwright/pages/SliceMachinePage.ts @@ -1,10 +1,12 @@ import { Locator, Page } from "@playwright/test"; -import { Menu } from "./Menu"; +import { Menu } from "./components/Menu"; +import { InAppGuideDialog } from "./components/InAppGuideDialog"; export class SliceMachinePage { readonly page: Page; readonly menu: Menu; + readonly inAppGuideDialog: InAppGuideDialog; readonly body: Locator; readonly breadcrumb: Locator; @@ -14,6 +16,7 @@ export class SliceMachinePage { */ this.page = page; this.menu = new Menu(page); + this.inAppGuideDialog = new InAppGuideDialog(page); /** * Static locators @@ -30,7 +33,9 @@ export class SliceMachinePage { /** * Actions */ - // Handle actions here + async gotoDefaultPage() { + await this.page.goto("/"); + } /** * Assertions diff --git a/playwright/pages/SlicesListPage.ts b/playwright/pages/SlicesListPage.ts index 67c4e32088..274f30e680 100644 --- a/playwright/pages/SlicesListPage.ts +++ b/playwright/pages/SlicesListPage.ts @@ -3,7 +3,7 @@ import { Locator, Page } from "@playwright/test"; import { CreateSliceDialog } from "./components/CreateSliceDialog"; import { RenameSliceDialog } from "./components/RenameSliceDialog"; import { DeleteSliceDialog } from "./components/DeleteSliceDialog"; -import { SliceMachinePage } from "./components/SliceMachinePage"; +import { SliceMachinePage } from "./SliceMachinePage"; export class SlicesListPage extends SliceMachinePage { readonly createSliceDialog: CreateSliceDialog; diff --git a/playwright/pages/components/Dialog.ts b/playwright/pages/components/Dialog.ts index 400c0b7b88..4025163afb 100644 --- a/playwright/pages/components/Dialog.ts +++ b/playwright/pages/components/Dialog.ts @@ -26,7 +26,7 @@ export class Dialog { * Static locators */ this.dialog = page.getByRole("dialog"); - this.title = this.dialog.getByRole("heading", { name: title, exact: true }); + this.title = this.dialog.getByText(title, { exact: true }); this.closeButton = this.dialog.getByRole("button", { name: "Close", exact: true, diff --git a/playwright/pages/components/InAppGuideDialog.ts b/playwright/pages/components/InAppGuideDialog.ts new file mode 100644 index 0000000000..27d9d6bfd6 --- /dev/null +++ b/playwright/pages/components/InAppGuideDialog.ts @@ -0,0 +1,38 @@ +import { Locator, Page } from "@playwright/test"; + +import { Dialog } from "./Dialog"; + +export class InAppGuideDialog extends Dialog { + override readonly closeButton: Locator; + + constructor(page: Page) { + super(page, { + title: "Build a page in 5 minutes", + }); + + /** + * Components + */ + // Handle components here + + /** + * Static locators + */ + this.closeButton = this.dialog.getByRole("button"); + } + + /** + * Dynamic locators + */ + // Handle dynamic locators here + + /** + * Actions + */ + // Handle actions here + + /** + * Assertions + */ + // Handle assertions here +} diff --git a/playwright/pages/shared/BuilderPage.ts b/playwright/pages/shared/BuilderPage.ts index e46655653a..cb7fcc8e61 100644 --- a/playwright/pages/shared/BuilderPage.ts +++ b/playwright/pages/shared/BuilderPage.ts @@ -1,7 +1,7 @@ import { expect, Locator, Page } from "@playwright/test"; import { AddFieldDialog } from "../components/AddFieldDialog"; -import { SliceMachinePage } from "../components/SliceMachinePage"; +import { SliceMachinePage } from "../SliceMachinePage"; export type FieldType = | "Rich Text" diff --git a/playwright/pages/shared/TypesTablePage.ts b/playwright/pages/shared/TypesTablePage.ts index 0a39abe2b5..600d11a10c 100644 --- a/playwright/pages/shared/TypesTablePage.ts +++ b/playwright/pages/shared/TypesTablePage.ts @@ -2,7 +2,7 @@ import { Locator, Page } from "@playwright/test"; import { CreateTypeDialog } from "../components/CreateTypeDialog"; import { RenameTypeDialog } from "../components/RenameTypeDialog"; -import { SliceMachinePage } from "../components/SliceMachinePage"; +import { SliceMachinePage } from "../SliceMachinePage"; export class TypesTablePage extends SliceMachinePage { readonly createTypeDialog: CreateTypeDialog; diff --git a/playwright/playwright.config.ts b/playwright/playwright.config.ts index 7a04be52f6..f9d6f0037e 100644 --- a/playwright/playwright.config.ts +++ b/playwright/playwright.config.ts @@ -17,16 +17,11 @@ const config = { // Configure projects for major browsers. projects: [ - { - name: "onboarding", - testMatch: "onboarding.spec.ts", - }, { name: "chromium", use: { ...devices["Desktop Chrome"], }, - dependencies: ["onboarding"], }, ], diff --git a/playwright/tests/changes/changes.spec.ts b/playwright/tests/changes/changes.spec.ts index a7f3e1ff06..25a104c21a 100644 --- a/playwright/tests/changes/changes.spec.ts +++ b/playwright/tests/changes/changes.spec.ts @@ -1,5 +1,134 @@ +import { expect } from "@playwright/test"; + import { test } from "../../fixtures"; +import { mockManagerProcedures } from "../../utils"; +import { emptyLibraries, simpleCustomType } from "../../mocks"; + +test.describe("Changes", () => { + test.run({ loggedIn: true })( + "I cannot see the login screen when logged in", + async ({ changesPage }) => { + await changesPage.goto(); + await expect(changesPage.loginButton).not.toBeVisible(); + await expect(changesPage.notLoggedInTitle).not.toBeVisible(); + await expect(changesPage.notAuthorizedTitle).not.toBeVisible(); + }, + ); + + test.run()( + "I can see the login screen when logged out", + async ({ changesPage }) => { + await changesPage.goto(); + await expect(changesPage.loginButton).toBeVisible(); + await expect(changesPage.notLoggedInTitle).toBeVisible(); + }, + ); + + test.run({ loggedIn: true })( + "I can see the unauthorized screen when not authorized", + async ({ changesPage }) => { + await mockManagerProcedures({ + page: changesPage.page, + procedures: [ + { + path: "getState", + data: (data) => ({ + ...data, + clientError: { + status: 403, + }, + }), + }, + ], + }); + + await changesPage.goto(); + await expect(changesPage.loginButton).not.toBeVisible(); + await expect(changesPage.notAuthorizedTitle).toBeVisible(); + }, + ); + + test.run({ loggedIn: true })( + "I can see the empty state when I don't have any changes to push", + async ({ changesPage }) => { + await mockManagerProcedures({ + page: changesPage.page, + procedures: [ + { + path: "getState", + data: (data) => ({ + ...data, + libraries: emptyLibraries, + customTypes: [], + remoteCustomTypes: [], + remoteSlices: [], + }), + }, + ], + }); + + await changesPage.goto(); + await expect(changesPage.loginButton).not.toBeVisible(); + await expect(changesPage.blankSlateTitle).toBeVisible(); + }, + ); + + test.run({ loggedIn: true })( + "I can see the changes I have to push", + async ({ changesPage }) => { + await mockManagerProcedures({ + page: changesPage.page, + procedures: [ + { + path: "getState", + data: (data) => ({ + ...data, + libraries: emptyLibraries, + customTypes: [simpleCustomType], + remoteCustomTypes: [], + remoteSlices: [], + }), + }, + ], + }); + + await changesPage.goto(); + await expect(changesPage.loginButton).not.toBeVisible(); + await changesPage.checkCustomTypeName( + simpleCustomType.id, + simpleCustomType.label, + ); + await changesPage.checkCustomTypeApiId(simpleCustomType.id); + await changesPage.checkCustomTypeStatus(simpleCustomType.id, "New"); + }, + ); + + test.run({ loggedIn: true })( + "I can push the changes I have", + async ({ changesPage }) => { + await mockManagerProcedures({ + page: changesPage.page, + procedures: [ + { + path: "getState", + data: (data) => ({ + ...data, + libraries: emptyLibraries, + customTypes: [simpleCustomType], + remoteCustomTypes: [], + remoteSlices: [], + }), + }, + { + path: "prismicRepository.pushChanges", + execute: false, + }, + ], + }); -test.describe.skip("Changes", () => { - // TODO: Add tests + await changesPage.goto(); + await expect(changesPage.loginButton).not.toBeVisible(); + await changesPage.pushChanges(); + }, + ); }); diff --git a/playwright/tests/common/inAppGuide.spec.ts b/playwright/tests/common/inAppGuide.spec.ts new file mode 100644 index 0000000000..a1289363cf --- /dev/null +++ b/playwright/tests/common/inAppGuide.spec.ts @@ -0,0 +1,39 @@ +import { expect } from "@playwright/test"; + +import { test } from "../../fixtures"; + +test.describe("In-app guide", () => { + test.run({ onboarded: false })( + "I can see the in-app guide open by default", + async ({ sliceMachinePage }) => { + await sliceMachinePage.gotoDefaultPage(); + + await expect(sliceMachinePage.inAppGuideDialog.title).toBeVisible(); + }, + ); + + test.run({ onboarded: false })( + "I can close the in-app guide", + async ({ sliceMachinePage }) => { + await sliceMachinePage.gotoDefaultPage(); + + await expect(sliceMachinePage.inAppGuideDialog.title).toBeVisible(); + await sliceMachinePage.inAppGuideDialog.closeButton.click(); + await expect(sliceMachinePage.inAppGuideDialog.title).not.toBeVisible(); + + await sliceMachinePage.page.reload(); + await expect(sliceMachinePage.inAppGuideDialog.title).not.toBeVisible(); + }, + ); + + test.run({ onboarded: false })( + "I can see the in-app guide on different pages", + async ({ sliceMachinePage, slicesListPage, customTypesTablePage }) => { + await slicesListPage.goto(); + await expect(sliceMachinePage.inAppGuideDialog.title).toBeVisible(); + + await customTypesTablePage.goto(); + await expect(sliceMachinePage.inAppGuideDialog.title).toBeVisible(); + }, + ); +}); diff --git a/playwright/tests/common/navigation.spec.ts b/playwright/tests/common/navigation.spec.ts index 7ef7dbccf8..e4109baa4e 100644 --- a/playwright/tests/common/navigation.spec.ts +++ b/playwright/tests/common/navigation.spec.ts @@ -3,46 +3,61 @@ import { expect } from "@playwright/test"; import { test } from "../../fixtures"; test.describe("Navigation", () => { - test("I can navigate through all menu entries", async ({ - page, - pageTypesTablePage, - customTypesTablePage, - slicesListPage, - changesPage, - changelogPage, - }) => { - await page.goto("/"); - - await pageTypesTablePage.menu.pageTypesLink.click(); - await expect(pageTypesTablePage.breadcrumbLabel).toBeVisible(); - expect(await page.title()).toContain("Page types - Slice Machine"); - - await customTypesTablePage.menu.customTypesLink.click(); - await expect(customTypesTablePage.breadcrumbLabel).toBeVisible(); - expect(await page.title()).toContain("Custom types - Slice Machine"); - - await slicesListPage.menu.slicesLink.click(); - await expect(slicesListPage.breadcrumbLabel).toBeVisible(); - expect(await page.title()).toContain("Slices - Slice Machine"); - - await changesPage.menu.changesLink.click(); - await expect(changesPage.breadcrumbLabel).toBeVisible(); - expect(await page.title()).toContain("Changes - Slice Machine"); - - await changelogPage.menu.changelogLink.click(); - await expect(changelogPage.breadcrumbLabel).toBeVisible(); - expect(await page.title()).toContain("Changelog - Slice Machine"); - }); + test.run()( + "I can navigate through all menu entries", + async ({ + sliceMachinePage, + pageTypesTablePage, + customTypesTablePage, + slicesListPage, + changesPage, + changelogPage, + }) => { + await sliceMachinePage.gotoDefaultPage(); + + await pageTypesTablePage.menu.pageTypesLink.click(); + await expect(pageTypesTablePage.breadcrumbLabel).toBeVisible(); + expect(await sliceMachinePage.page.title()).toContain( + "Page types - Slice Machine", + ); + + await customTypesTablePage.menu.customTypesLink.click(); + await expect(customTypesTablePage.breadcrumbLabel).toBeVisible(); + expect(await sliceMachinePage.page.title()).toContain( + "Custom types - Slice Machine", + ); + + await slicesListPage.menu.slicesLink.click(); + await expect(slicesListPage.breadcrumbLabel).toBeVisible(); + expect(await sliceMachinePage.page.title()).toContain( + "Slices - Slice Machine", + ); + + await changesPage.menu.changesLink.click(); + await expect(changesPage.breadcrumbLabel).toBeVisible(); + expect(await sliceMachinePage.page.title()).toContain( + "Changes - Slice Machine", + ); + + await changelogPage.menu.changelogLink.click(); + await expect(changelogPage.breadcrumbLabel).toBeVisible(); + expect(await sliceMachinePage.page.title()).toContain( + "Changelog - Slice Machine", + ); + }, + ); // Unskip when we fix the Changelog fetching problem - DT-1794 - test.skip("I access the changelog from Slice Machine version", async ({ - pageTypesTablePage, - changelogPage, - }) => { - await pageTypesTablePage.goto(); - await expect(pageTypesTablePage.menu.appVersion).toBeVisible(); - await pageTypesTablePage.menu.appVersion.click(); - - await expect(changelogPage.breadcrumbLabel).toBeVisible(); - }); + test + .run() + .skip( + "I access the changelog from Slice Machine version", + async ({ pageTypesTablePage, changelogPage }) => { + await pageTypesTablePage.goto(); + await expect(pageTypesTablePage.menu.appVersion).toBeVisible(); + await pageTypesTablePage.menu.appVersion.click(); + + await expect(changelogPage.breadcrumbLabel).toBeVisible(); + }, + ); }); diff --git a/playwright/tests/common/notLoggedIn.spec.ts b/playwright/tests/common/notLoggedIn.spec.ts deleted file mode 100644 index cc5918efcb..0000000000 --- a/playwright/tests/common/notLoggedIn.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { test } from "@playwright/test"; - -test.describe.skip("Not logged in", () => { - // TODO: Add tests -}); diff --git a/playwright/tests/common/onboarding.spec.ts b/playwright/tests/common/onboarding.spec.ts deleted file mode 100644 index ef7a98ba08..0000000000 --- a/playwright/tests/common/onboarding.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Fixtures, test as baseTest } from "../../fixtures"; - -import config from "../../playwright.config"; - -// Extend the base test to ensure user is not onboarded -const test = baseTest.extend({ - page: async ({ browser }, use) => { - const context = await browser.newContext({ - storageState: { - cookies: [], - origins: [ - { - origin: config.use.baseURL, - localStorage: [], - }, - ], - }, - }); - const page = await context.newPage(); - - // Use the fixture value in the test. - await use(page); - - // Gracefully close up everything - await page.close(); - await context.close(); - }, -}); - -test.describe.skip("Onboarding", () => { - // TODO: Add tests -}); diff --git a/playwright/tests/customTypes/customTypes.spec.ts b/playwright/tests/customTypes/customTypes.spec.ts index 4d86aaa05b..cfa7a735a1 100644 --- a/playwright/tests/customTypes/customTypes.spec.ts +++ b/playwright/tests/customTypes/customTypes.spec.ts @@ -4,40 +4,42 @@ import { test } from "../../fixtures"; import { generateRandomId } from "../../utils/generateRandomId"; test.describe("Custom Types", () => { - test("I can create a custom type", async ({ - customTypesTablePage, - customTypesBuilderPage, - }) => { - await customTypesTablePage.goto(); - await customTypesTablePage.openCreateDialog(); - - const name = "Custom Type " + generateRandomId(); - await customTypesTablePage.createTypeDialog.createType(name); - - // TODO(DT-1801): Production BUG - When creating a custom type, don't redirect - // to the builder page until the custom type is created - await customTypesBuilderPage.goto(name); - - await expect( - customTypesBuilderPage.breadcrumb.getByText(name), - ).toBeVisible(); - - await expect(customTypesBuilderPage.staticZoneListItem).toHaveCount(1); - }); - - test("I can rename a custom type", async ({ - customType, - customTypesTablePage, - }) => { - await customTypesTablePage.goto(); - await customTypesTablePage.openActionDialog(customType.name, "Rename"); - - const newCustomTypeName = `${customType.name}Renamed`; - await customTypesTablePage.renameTypeDialog.renameType(newCustomTypeName); - - // TODO(DT-1802): Production BUG - Sometimes after a rename, old custom type name is still visible in the list - await customTypesTablePage.page.reload(); - - await expect(customTypesTablePage.getRow(newCustomTypeName)).toBeVisible(); - }); + test.run()( + "I can create a custom type", + async ({ customTypesTablePage, customTypesBuilderPage }) => { + await customTypesTablePage.goto(); + await customTypesTablePage.openCreateDialog(); + + const name = "Custom Type " + generateRandomId(); + await customTypesTablePage.createTypeDialog.createType(name); + + // TODO(DT-1801): Production BUG - When creating a custom type, don't redirect + // to the builder page until the custom type is created + await customTypesBuilderPage.goto(name); + + await expect( + customTypesBuilderPage.breadcrumb.getByText(name), + ).toBeVisible(); + + await expect(customTypesBuilderPage.staticZoneListItem).toHaveCount(1); + }, + ); + + test.run()( + "I can rename a custom type", + async ({ customType, customTypesTablePage }) => { + await customTypesTablePage.goto(); + await customTypesTablePage.openActionDialog(customType.name, "Rename"); + + const newCustomTypeName = `${customType.name}Renamed`; + await customTypesTablePage.renameTypeDialog.renameType(newCustomTypeName); + + // TODO(DT-1802): Production BUG - Sometimes after a rename, old custom type name is still visible in the list + await customTypesTablePage.page.reload(); + + await expect( + customTypesTablePage.getRow(newCustomTypeName), + ).toBeVisible(); + }, + ); }); diff --git a/playwright/tests/slices/sliceBuilder.spec.ts b/playwright/tests/slices/sliceBuilder.spec.ts index 28a0da8ff9..1f5fff83fb 100644 --- a/playwright/tests/slices/sliceBuilder.spec.ts +++ b/playwright/tests/slices/sliceBuilder.spec.ts @@ -3,20 +3,20 @@ import { expect } from "@playwright/test"; import { test } from "../../fixtures"; test.describe("Slice Builder", () => { - test("I can add a static field to the builder", async ({ - slice, - sliceBuilderPage, - }) => { - await sliceBuilderPage.goto(slice.name); + test.run()( + "I can add a static field to the builder", + async ({ slice, sliceBuilderPage }) => { + await sliceBuilderPage.goto(slice.name); - await expect(sliceBuilderPage.staticZoneListItem).toHaveCount(0); + await expect(sliceBuilderPage.staticZoneListItem).toHaveCount(0); - await sliceBuilderPage.addStaticField( - "Rich Text", - "Description", - "description", - ); + await sliceBuilderPage.addStaticField( + "Rich Text", + "Description", + "description", + ); - await expect(sliceBuilderPage.staticZoneListItem).toHaveCount(1); - }); + await expect(sliceBuilderPage.staticZoneListItem).toHaveCount(1); + }, + ); }); diff --git a/playwright/tests/slices/slices.spec.ts b/playwright/tests/slices/slices.spec.ts index 30fe48f7d2..910d1f0248 100644 --- a/playwright/tests/slices/slices.spec.ts +++ b/playwright/tests/slices/slices.spec.ts @@ -4,69 +4,72 @@ import { test } from "../../fixtures"; import { generateRandomId } from "../../utils/generateRandomId"; test.describe("Slices", () => { - test("I can create a slice", async ({ sliceBuilderPage, slicesListPage }) => { - await slicesListPage.goto(); - await slicesListPage.openCreateDialog(); - - const sliceName = "Slice" + generateRandomId(); - await slicesListPage.createSliceDialog.createSlice(sliceName); - - await expect( - sliceBuilderPage.breadcrumb.getByText(sliceName), - ).toBeVisible(); - - await expect(sliceBuilderPage.staticZoneListItem).toHaveCount(0); - await expect(sliceBuilderPage.repeatableZoneListItem).toHaveCount(0); - }); - - test("I can only use Pascal case for the slice name", async ({ - slicesListPage, - }) => { - await slicesListPage.goto(); - await slicesListPage.openCreateDialog(); - - const { nameInput, submitButton } = slicesListPage.createSliceDialog; - - await nameInput.fill("Invalid Slice Name"); - await expect(submitButton).toBeDisabled(); - - await nameInput.clear(); - await nameInput.fill("Invalid_slice_name"); - await expect(submitButton).toBeDisabled(); - - await nameInput.clear(); - await nameInput.fill("123SliceName"); - await expect(submitButton).toBeDisabled(); - - await nameInput.clear(); - await nameInput.fill("ValidSliceName"); - await expect(submitButton).toBeEnabled(); - }); - - test("I can rename a slice and open the slice after", async ({ - slice, - sliceBuilderPage, - slicesListPage, - }) => { - await slicesListPage.goto(); - await slicesListPage.openActionDialog(slice.name, "Rename"); - - const newSliceName = `${slice.name}Renamed`; - await slicesListPage.renameSliceDialog.renameSlice(newSliceName); - - // TODO(DT-1802): Production BUG - Sometimes after a rename, old slice name is still visible in the list - await slicesListPage.page.reload(); - - await expect(slicesListPage.getCard(slice.name)).not.toBeVisible(); - await expect(slicesListPage.getCard(newSliceName)).toBeVisible(); - await slicesListPage.clickCard(newSliceName); - - await expect( - sliceBuilderPage.breadcrumb.getByText(newSliceName), - ).toBeVisible(); - }); - - test("I can delete a slice", async ({ slice, slicesListPage }) => { + test.run()( + "I can create a slice", + async ({ sliceBuilderPage, slicesListPage }) => { + await slicesListPage.goto(); + await slicesListPage.openCreateDialog(); + + const sliceName = "Slice" + generateRandomId(); + await slicesListPage.createSliceDialog.createSlice(sliceName); + + await expect( + sliceBuilderPage.breadcrumb.getByText(sliceName), + ).toBeVisible(); + + await expect(sliceBuilderPage.staticZoneListItem).toHaveCount(0); + await expect(sliceBuilderPage.repeatableZoneListItem).toHaveCount(0); + }, + ); + + test.run()( + "I can only use Pascal case for the slice name", + async ({ slicesListPage }) => { + await slicesListPage.goto(); + await slicesListPage.openCreateDialog(); + + const { nameInput, submitButton } = slicesListPage.createSliceDialog; + + await nameInput.fill("Invalid Slice Name"); + await expect(submitButton).toBeDisabled(); + + await nameInput.clear(); + await nameInput.fill("Invalid_slice_name"); + await expect(submitButton).toBeDisabled(); + + await nameInput.clear(); + await nameInput.fill("123SliceName"); + await expect(submitButton).toBeDisabled(); + + await nameInput.clear(); + await nameInput.fill("ValidSliceName"); + await expect(submitButton).toBeEnabled(); + }, + ); + + test.run()( + "I can rename a slice and open the slice after", + async ({ slice, sliceBuilderPage, slicesListPage }) => { + await slicesListPage.goto(); + await slicesListPage.openActionDialog(slice.name, "Rename"); + + const newSliceName = `${slice.name}Renamed`; + await slicesListPage.renameSliceDialog.renameSlice(newSliceName); + + // TODO(DT-1802): Production BUG - Sometimes after a rename, old slice name is still visible in the list + await slicesListPage.page.reload(); + + await expect(slicesListPage.getCard(slice.name)).not.toBeVisible(); + await expect(slicesListPage.getCard(newSliceName)).toBeVisible(); + await slicesListPage.clickCard(newSliceName); + + await expect( + sliceBuilderPage.breadcrumb.getByText(newSliceName), + ).toBeVisible(); + }, + ); + + test.run()("I can delete a slice", async ({ slice, slicesListPage }) => { await slicesListPage.goto(); await slicesListPage.openActionDialog(slice.name, "Delete"); diff --git a/playwright/tsconfig.json b/playwright/tsconfig.json index a974f06c26..1ad3b4bfa8 100644 --- a/playwright/tsconfig.json +++ b/playwright/tsconfig.json @@ -17,6 +17,7 @@ }, "include": [ "fixtures", + "mocks", "pages", "tests", "utils", diff --git a/playwright/utils/index.ts b/playwright/utils/index.ts new file mode 100644 index 0000000000..0641616f40 --- /dev/null +++ b/playwright/utils/index.ts @@ -0,0 +1,2 @@ +export { generateRandomId } from "./generateRandomId"; +export { mockManagerProcedures } from "./mockManagerProcedures"; diff --git a/playwright/utils/mockManagerProcedures.ts b/playwright/utils/mockManagerProcedures.ts new file mode 100644 index 0000000000..79a40f8c4b --- /dev/null +++ b/playwright/utils/mockManagerProcedures.ts @@ -0,0 +1,83 @@ +import { encode, decode } from "@msgpack/msgpack"; +import { Page } from "@playwright/test"; + +type MockManagerProceduresArgs = { + /** + * Playwright page object + */ + page: Page; + + /** + * Array of procedures to mock + */ + procedures: { + /** + * Procedure path in the format of `getState` or `slices.readSlice` + */ + path: string; + + /** + * Function that takes the existing data and returns the data to return. + */ + data?: (data: Record) => Record; + + /** + * Whether to execute the procedure or not. Defaults to true. + */ + execute?: boolean; + }[]; +}; + +/** + * Mocks manager procedures from SliceMachineManagerClient + */ +export async function mockManagerProcedures(args: MockManagerProceduresArgs) { + const { page, procedures } = args; + + await page.route("*/**/_manager", async (route) => { + const postDataBuffer = route.request().postDataBuffer() as Buffer; + const postData = decode(postDataBuffer) as Record< + "procedurePath", + unknown[] + >; + + const procedure = procedures.find( + (p) => p.path === postData.procedurePath.join("."), + ); + + if (procedure) { + const { data, execute = true } = procedure; + + let newBody = Buffer.from( + encode( + data + ? { + data: data({}), + } + : {}, + ), + ); + + if (execute) { + const response = await route.fetch(); + const existingBody = await response.body(); + const existingData = (decode(existingBody) as Record<"data", unknown>) + .data as Record; + + if (data) { + newBody = Buffer.from( + encode({ + data: data(existingData), + }), + ); + } + } + + await route.fulfill({ + body: newBody, + }); + } else { + await route.continue(); + } + }); +} diff --git a/yarn.lock b/yarn.lock index 4b5b9ecf58..4c3bb83b47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3751,7 +3751,7 @@ __metadata: languageName: node linkType: hard -"@msgpack/msgpack@npm:^2.8.0": +"@msgpack/msgpack@npm:2.8.0, @msgpack/msgpack@npm:^2.8.0": version: 2.8.0 resolution: "@msgpack/msgpack@npm:2.8.0" checksum: bead9393f57239007a2fe455df5277cbc03b125f14f310162a652b81471dcf3ab6780eaa24b36e20aa742998910a6840147d08b7267063b8e2de5d40c624360e @@ -5085,30 +5085,21 @@ __metadata: languageName: node linkType: hard -"@prismicio/custom-types-client@npm:1.2.0-alpha.0": - version: 1.2.0-alpha.0 - resolution: "@prismicio/custom-types-client@npm:1.2.0-alpha.0" - dependencies: - "@prismicio/types": ^0.2.4 - checksum: fa58201a97d211c3d5a691b7fe667e84382a6abb4b6e9ffc4bc27d33112ce4b776351b822155a707b97c07d63f93d994f3c55a93d0c9726972f74b3b4c103b09 - languageName: node - linkType: hard - -"@prismicio/custom-types-client@npm:^1.1.0": - version: 1.1.0 - resolution: "@prismicio/custom-types-client@npm:1.1.0" +"@prismicio/custom-types-client@npm:^1.1.0, @prismicio/custom-types-client@npm:^1.2.0": + version: 1.2.0 + resolution: "@prismicio/custom-types-client@npm:1.2.0" dependencies: "@prismicio/types": ^0.2.4 - checksum: e5befbfa85cbd6ab9811c2cae9eba35c138d30f5d87ad59087e5f109f7088e34ab38e6993651edee7359aca88997da2ed92996803fe40b56a37d5f2311d2e11c + checksum: b1344054a40dddee1845a65fc747d9208c1bdc8ef4ff368f9e75e602ea4781b9dbf1d22b5a74488a6702c5169370e567892ea484fdbbeac2247998abb0b329e2 languageName: node linkType: hard -"@prismicio/editor-fields@npm:0.4.16": - version: 0.4.16 - resolution: "@prismicio/editor-fields@npm:0.4.16" +"@prismicio/editor-fields@npm:0.4.17": + version: 0.4.17 + resolution: "@prismicio/editor-fields@npm:0.4.17" dependencies: "@floating-ui/react-dom-interactions": 0.9.3 - "@prismicio/editor-support": 0.4.16 + "@prismicio/editor-support": 0.4.17 "@prismicio/richtext": 2.1.1 "@prismicio/types-internal": 2.3.1 "@tiptap/core": 2.0.3 @@ -5139,16 +5130,16 @@ __metadata: uuid: 9.0.0 zod: 3.21.4 peerDependencies: - "@prismicio/editor-ui": ^0.4.16 + "@prismicio/editor-ui": ^0.4.17 react: 18 react-dom: 18 - checksum: b64312126a221160fa55c80525c6797d7c07c6ada98f06fbc00dc37b0cc0572a1ec8f31af3c7a99a7ec2b339e0b1124ab125aedc0ac958126c23958a66bfbccf + checksum: 3f5acdb0859af0681975d98e7779b0bb5f082d4b07b2d9c5e886d528ac11e083bc536c3bd7469b3cee6b39e91208a87140ec8dbc1ffbf2182d93f37b7a3e3779 languageName: node linkType: hard -"@prismicio/editor-support@npm:0.4.16": - version: 0.4.16 - resolution: "@prismicio/editor-support@npm:0.4.16" +"@prismicio/editor-support@npm:0.4.17": + version: 0.4.17 + resolution: "@prismicio/editor-support@npm:0.4.17" dependencies: "@prismicio/types-internal": 2.3.1 io-ts: 2.2.18 @@ -5161,16 +5152,16 @@ __metadata: optional: true zod: optional: true - checksum: f8645fe7cf7be97754bc036b907fbaa4aa652d082b1d517db4c2a5b2962315cd10921179ebb164865f1f72b4f37d5a8d0ddc69d88b3276a8d6bc5d30964a800b + checksum: 0b435944216f45049c98b903fe696d272fa73cd0cea50ef8ce86018faeb3b2c3e57346b85aef28287169f1a701d9f3cf6e7b25a0079eaf3770f5e6bddb767ecb languageName: node linkType: hard -"@prismicio/editor-ui@npm:0.4.16": - version: 0.4.16 - resolution: "@prismicio/editor-ui@npm:0.4.16" +"@prismicio/editor-ui@npm:0.4.17": + version: 0.4.17 + resolution: "@prismicio/editor-ui@npm:0.4.17" dependencies: "@internationalized/date": 3.5.0 - "@prismicio/editor-support": 0.4.16 + "@prismicio/editor-support": 0.4.17 "@radix-ui/react-avatar": 1.0.4 "@radix-ui/react-checkbox": 1.0.4 "@radix-ui/react-dialog": 1.0.5 @@ -5202,13 +5193,14 @@ __metadata: "@vanilla-extract/css-utils": 0.1.2 "@vanilla-extract/sprinkles": 1.5.0 clsx: 1.2.1 + csstype: 3.1.2 react-easy-crop: 4.6.1 react-remove-scroll: 2.5.6 tslib: 2.4.0 peerDependencies: react: 17 || 18 react-dom: 17 || 18 - checksum: 29cbbab97f35acf696d255ed3b2145c98b4c3bb16c4daecaed749345546e6806b031ab02bd773491b914b212b695c5afba50352b7e74f9c053c2aa9a8eca8309 + checksum: 218eaf83fada838776ba4e3c0a5806487479768fd05e153f1677e3e12034bf2416ccf4f2450e87ad52d31bd3c790f8038e5ef3eeaaaade01e004eb9e05d7ca38 languageName: node linkType: hard @@ -8543,10 +8535,12 @@ __metadata: version: 0.0.0-use.local resolution: "@slicemachine/e2e@workspace:playwright" dependencies: + "@msgpack/msgpack": 2.8.0 "@playwright/test": 1.39.0 "@typescript-eslint/eslint-plugin": 5.55.0 "@typescript-eslint/parser": 5.55.0 depcheck: 1.4.3 + dotenv: 16.3.1 eslint: 8.37.0 eslint-config-prettier: 9.0.0 typescript: 4.9.5 @@ -8607,7 +8601,7 @@ __metadata: resolution: "@slicemachine/manager@workspace:packages/manager" dependencies: "@antfu/ni": ^0.20.0 - "@prismicio/custom-types-client": 1.2.0-alpha.0 + "@prismicio/custom-types-client": ^1.2.0 "@prismicio/mock": 0.2.0 "@prismicio/mocks": 2.0.0 "@prismicio/types-internal": ^2.2.0 @@ -15991,6 +15985,13 @@ __metadata: languageName: node linkType: hard +"csstype@npm:3.1.2, csstype@npm:^3.0.10, csstype@npm:^3.0.2, csstype@npm:^3.0.7, csstype@npm:^3.1.0, csstype@npm:^3.1.1": + version: 3.1.2 + resolution: "csstype@npm:3.1.2" + checksum: e1a52e6c25c1314d6beef5168da704ab29c5186b877c07d822bd0806717d9a265e8493a2e35ca7e68d0f5d472d43fac1cdce70fd79fd0853dff81f3028d857b5 + languageName: node + linkType: hard + "csstype@npm:^2.6.8": version: 2.6.21 resolution: "csstype@npm:2.6.21" @@ -15998,13 +15999,6 @@ __metadata: languageName: node linkType: hard -"csstype@npm:^3.0.10, csstype@npm:^3.0.2, csstype@npm:^3.0.7, csstype@npm:^3.1.0, csstype@npm:^3.1.1": - version: 3.1.2 - resolution: "csstype@npm:3.1.2" - checksum: e1a52e6c25c1314d6beef5168da704ab29c5186b877c07d822bd0806717d9a265e8493a2e35ca7e68d0f5d472d43fac1cdce70fd79fd0853dff81f3028d857b5 - languageName: node - linkType: hard - "cuint@npm:^0.2.2": version: 0.2.2 resolution: "cuint@npm:0.2.2" @@ -31369,9 +31363,9 @@ __metadata: dependencies: "@emotion/react": 11.11.1 "@extractus/oembed-extractor": 3.1.8 - "@prismicio/editor-fields": 0.4.16 - "@prismicio/editor-support": 0.4.16 - "@prismicio/editor-ui": 0.4.16 + "@prismicio/editor-fields": 0.4.17 + "@prismicio/editor-support": 0.4.17 + "@prismicio/editor-ui": 0.4.17 "@prismicio/mocks": 2.0.0-alpha.2 "@prismicio/simulator": 0.1.4 "@prismicio/types-internal": 2.2.0