From fe2306d161b505aabfc82e4f0415faf7a8b772c7 Mon Sep 17 00:00:00 2001 From: Victoria Bergquist Date: Fri, 4 Feb 2022 14:38:47 +0100 Subject: [PATCH] test(desk-tool): port document actions tests to playwright --- dev/test-studio/parts/deskStructure.js | 2 +- .../schema/ci/documentActionsCI.js | 12 ++ dev/test-studio/schema/index.js | 2 + package.json | 7 +- .../src/components/ConfirmDelete.tsx | 1 + .../src/components/ConfirmUnpublish.tsx | 1 + .../src/components/paneItem/PaneItem.tsx | 3 +- playwright.config.ts | 4 +- playwright/global-setup.ts | 8 +- playwright/helpers/constants.ts | 4 + playwright/helpers/createUniqueDocument.ts | 18 +++ playwright/helpers/index.ts | 3 + playwright/helpers/sanityClient.ts | 21 +++ .../tests/desk-tool/documentActions.spec.ts | 121 ++++++++++++++++++ 14 files changed, 197 insertions(+), 10 deletions(-) create mode 100644 dev/test-studio/schema/ci/documentActionsCI.js create mode 100644 playwright/helpers/constants.ts create mode 100644 playwright/helpers/createUniqueDocument.ts create mode 100644 playwright/helpers/index.ts create mode 100644 playwright/helpers/sanityClient.ts create mode 100644 playwright/tests/desk-tool/documentActions.spec.ts diff --git a/dev/test-studio/parts/deskStructure.js b/dev/test-studio/parts/deskStructure.js index 278e897fadd..c7ce53731ed 100644 --- a/dev/test-studio/parts/deskStructure.js +++ b/dev/test-studio/parts/deskStructure.js @@ -93,7 +93,7 @@ const DEBUG_INPUT_TYPES = [ 'withDocumentTest', ] -const CI_INPUT_TYPES = ['conditionalFieldset', 'validationCI'] +const CI_INPUT_TYPES = ['conditionalFieldset', 'validationCI', 'documentActionsCI'] const FIELD_GROUP_TYPES = [ 'fieldGroups', 'fieldGroupsDefault', diff --git a/dev/test-studio/schema/ci/documentActionsCI.js b/dev/test-studio/schema/ci/documentActionsCI.js new file mode 100644 index 00000000000..d8fc3f113f1 --- /dev/null +++ b/dev/test-studio/schema/ci/documentActionsCI.js @@ -0,0 +1,12 @@ +export default { + name: 'documentActionsCI', + title: 'Document Actions CI', + type: 'document', + fields: [ + { + name: 'title', + title: 'Title', + type: 'string', + }, + ], +} diff --git a/dev/test-studio/schema/index.js b/dev/test-studio/schema/index.js index afab60caf7d..ed13ae70b21 100644 --- a/dev/test-studio/schema/index.js +++ b/dev/test-studio/schema/index.js @@ -90,6 +90,7 @@ import species from './species' // CI documents import conditionalFieldset from './ci/conditionalFieldset' import validationTest from './ci/validationCI' +import documentActionsCI from './ci/documentActionsCI' export default createSchema({ name: 'test-examples', @@ -180,5 +181,6 @@ export default createSchema({ fieldGroupsDefault, fieldGroupsMany, fieldGroupsWithValidation, + documentActionsCI, ]), }) diff --git a/package.json b/package.json index 9aa114c0ce8..debc93b5055 100644 --- a/package.json +++ b/package.json @@ -65,9 +65,9 @@ "@babel/preset-typescript": "^7.10.4", "@babel/register": "^7.7.4", "@babel/runtime": "7.12.13", - "@sanity/client": "^3.0.1", "@cypress/code-coverage": "^3.9.12", - "@playwright/test": "^1.18.1", + "@playwright/test": "^1.19.1", + "@sanity/client": "^3.0.1", "@types/jest": "^27.0.1", "@types/node": "^14.18.9", "@types/react": "^17.0.0", @@ -140,5 +140,8 @@ "hooks": { "commitmsg": "node scripts/validateCommitMessage.js" } + }, + "dependencies": { + "playright": "^0.0.22" } } diff --git a/packages/@sanity/desk-tool/src/components/ConfirmDelete.tsx b/packages/@sanity/desk-tool/src/components/ConfirmDelete.tsx index 28041c3b532..32bc5905c03 100644 --- a/packages/@sanity/desk-tool/src/components/ConfirmDelete.tsx +++ b/packages/@sanity/desk-tool/src/components/ConfirmDelete.tsx @@ -43,6 +43,7 @@ export const ConfirmDelete = enhanceWithReferringDocuments(function ConfirmDelet onClick={onConfirm} text={hasReferringDocuments ? 'Delete anyway' : 'Delete now'} tone="critical" + data-testid="confirm-delete" /> )} diff --git a/packages/@sanity/desk-tool/src/components/ConfirmUnpublish.tsx b/packages/@sanity/desk-tool/src/components/ConfirmUnpublish.tsx index d10de8670e4..c6ca79d7274 100644 --- a/packages/@sanity/desk-tool/src/components/ConfirmUnpublish.tsx +++ b/packages/@sanity/desk-tool/src/components/ConfirmUnpublish.tsx @@ -42,6 +42,7 @@ export const ConfirmUnpublish = enhanceWithReferringDocuments(function ConfirmUn onClick={onConfirm} text={hasReferringDocuments ? 'Try to unpublish anyway' : 'Unpublish now'} tone="critical" + data-testid="confirm-unpublish" /> )} diff --git a/packages/@sanity/desk-tool/src/components/paneItem/PaneItem.tsx b/packages/@sanity/desk-tool/src/components/paneItem/PaneItem.tsx index d4c42d8e6e9..c1ebe976083 100644 --- a/packages/@sanity/desk-tool/src/components/paneItem/PaneItem.tsx +++ b/packages/@sanity/desk-tool/src/components/paneItem/PaneItem.tsx @@ -89,10 +89,11 @@ export function PaneItem(props: PaneItemProps) { pressed={pressed} selected={selected || clicked} tone="inherit" + data-testid={`pane-item-${id}`} > {preview} ), - [clicked, handleClick, LinkComponent, pressed, preview, selected] + [clicked, handleClick, LinkComponent, pressed, preview, selected, id] ) } diff --git a/playwright.config.ts b/playwright.config.ts index 60d6203795a..c0f6e1c7fd5 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,6 +8,8 @@ const config: PlaywrightTestConfig = { testDir: './playwright/tests', /* Maximum time one test can run for. */ // timeout: 30 * 1000 * 1000, + // Limit the number of failures on CI to save resources + maxFailures: process.env.CI ? 10 : undefined, expect: { /** @@ -91,7 +93,7 @@ const config: PlaywrightTestConfig = { ], /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - outputDir: 'test-results/', + outputDir: 'playwright/test-results/', /* Run your local dev server before starting the tests */ // webServer: { diff --git a/playwright/global-setup.ts b/playwright/global-setup.ts index 89785c9c27b..80ec14d4f2a 100644 --- a/playwright/global-setup.ts +++ b/playwright/global-setup.ts @@ -1,7 +1,3 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -/* eslint-disable no-process-env */ -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ - import {chromium, FullConfig} from '@playwright/test' require('dotenv').config() @@ -11,12 +7,13 @@ require('dotenv').config() * are logged in for all tests */ -async function globalSetup(config: FullConfig) { +async function globalSetup(config: FullConfig): Promise { const {baseURL} = config.projects[0].use const browser = await chromium.launch() // Create context to store our session token cookie in const context = await browser.newContext() const page = await context.newPage() + // eslint-disable-next-line no-process-env const token = process.env.PLAYWRIGHT_SANITY_SESSION_TOKEN if (!token) { @@ -26,6 +23,7 @@ async function globalSetup(config: FullConfig) { const [response] = await Promise.all([ page.waitForResponse('*/**/users/me*'), // This action triggers the request + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion page.goto(baseURL!), ]) diff --git a/playwright/helpers/constants.ts b/playwright/helpers/constants.ts new file mode 100644 index 00000000000..6d6aadf18e3 --- /dev/null +++ b/playwright/helpers/constants.ts @@ -0,0 +1,4 @@ +export const DEFAULT_DATASET_NAME = 'test' +export const STUDIO_SPACE_NAME = 'test' +export const STUDIO_PROJECT_ID = 'ppsg7ml5' +export const STALE_TEST_THRESHOLD_MS = 10000 diff --git a/playwright/helpers/createUniqueDocument.ts b/playwright/helpers/createUniqueDocument.ts new file mode 100644 index 00000000000..160d2a1e93e --- /dev/null +++ b/playwright/helpers/createUniqueDocument.ts @@ -0,0 +1,18 @@ +import {SanityDocument, SanityDocumentStub} from '@sanity/client' +import {uuid} from '@sanity/uuid' +import {testSanityClient} from '.' + +export async function createUniqueDocument({ + _type, + _id, + ...restProps +}: SanityDocumentStub): Promise> { + const doc = { + _type, + _id: _id || uuid(), + ...restProps, + } + + await testSanityClient.create(doc) + return doc +} diff --git a/playwright/helpers/index.ts b/playwright/helpers/index.ts new file mode 100644 index 00000000000..e1c2738a6f7 --- /dev/null +++ b/playwright/helpers/index.ts @@ -0,0 +1,3 @@ +export * from './sanityClient' +export * from './createUniqueDocument' +export * from './constants' diff --git a/playwright/helpers/sanityClient.ts b/playwright/helpers/sanityClient.ts new file mode 100644 index 00000000000..3839da30999 --- /dev/null +++ b/playwright/helpers/sanityClient.ts @@ -0,0 +1,21 @@ +import sanityClient from '@sanity/client' +import {STALE_TEST_THRESHOLD_MS} from './constants' + +export const testSanityClient = sanityClient({ + projectId: 'ppsg7ml5', + dataset: 'test', + token: process.env.PLAYWRIGHT_SANITY_SESSION_TOKEN, + useCdn: false, + apiVersion: '2021-08-31', +}) + +export function deleteDocumentsForRun( + typeName: string, + runId: string +): {query: string; params: Record} { + const threshold = new Date(Date.now() - STALE_TEST_THRESHOLD_MS).toISOString() + return { + query: `*[_type == $typeName && (runId == $runId || _createdAt < "${threshold}")]`, + params: {typeName, runId}, + } +} diff --git a/playwright/tests/desk-tool/documentActions.spec.ts b/playwright/tests/desk-tool/documentActions.spec.ts new file mode 100644 index 00000000000..59d52966474 --- /dev/null +++ b/playwright/tests/desk-tool/documentActions.spec.ts @@ -0,0 +1,121 @@ +import {test, expect} from '@playwright/test' +import {uuid} from '@sanity/uuid' +import { + createUniqueDocument, + deleteDocumentsForRun, + STUDIO_SPACE_NAME, + testSanityClient, +} from '../../helpers' + +/* + **NOTE** + In these tests, we are not testing input events, so to speed up the tests we will use use `page.fill()` instead of `page.type()` to populate the document title field +*/ + +const TITLE_PREFIX = '[playwright]' +const inputSelector = 'data-testid=input-title >> input' +const TEST_RUN_ID = uuid() + +// The path of the network request we want to wait for +const documentActionPath = (action) => + `*/**/mutate/${STUDIO_SPACE_NAME}?tag=sanity.studio.document.${action}*` + +// Helper to generate path to document created in a test +const generateDocumentUri = (docId) => + `/${STUDIO_SPACE_NAME}/desk/input-ci;documentActionsCI;${docId}` + +// Helper to get the path to the document the client creates for a test +async function createDocument( + _type: string, + title?: string +): Promise<{studioPath: string; documentId: string}> { + const testDoc = await createUniqueDocument({runId: TEST_RUN_ID, _type, title}) + return {studioPath: generateDocumentUri(testDoc?._id), documentId: testDoc?._id} +} + +test.describe('@sanity/desk-tool: document actions', () => { + test.afterEach(async ({page}, testInfo) => { + // After each test, delete the created documents if the test passed + if (testInfo.status === testInfo.expectedStatus) { + await testSanityClient.delete(deleteDocumentsForRun('documentActionsCI', TEST_RUN_ID)) + } + }) + + test('edit and publish document', async ({page}, testInfo) => { + const title = `${TITLE_PREFIX} ${testInfo.title}` + const publishButton = page.locator('data-testid=action-Publish') + const {studioPath} = await createDocument('documentActionsCI') + await page.goto(studioPath) + await page.fill(inputSelector, title) + expect(page.locator(inputSelector)).toHaveValue(title) + expect(publishButton).toBeEnabled() + expect(publishButton).toHaveAttribute('data-disabled', 'false') + + await Promise.all([page.waitForResponse(documentActionPath('publish')), publishButton.click()]) + await expect(publishButton).toBeDisabled() + await expect(publishButton).toHaveAttribute('data-disabled', 'true') + }) + + test('unpublish document', async ({page}, testInfo) => { + const title = `${TITLE_PREFIX} ${testInfo.title}` + const publishButton = page.locator('data-testid=action-Publish') + + const {studioPath} = await createDocument('documentActionsCI', title) + + await page.goto(studioPath) + + expect(page.locator(inputSelector)).toHaveValue(title) + await expect(publishButton).toBeDisabled() + expect(publishButton).toHaveAttribute('data-disabled', 'true') + page.locator('data-testid=action-menu-button').click() + page.locator('data-testid=action-Unpublish').click() + + await Promise.all([ + page.waitForResponse(documentActionPath('unpublish')), + page.locator('data-testid=confirm-unpublish').click(), + ]) + + await expect(publishButton).toBeEnabled() + await expect(publishButton).toHaveAttribute('data-disabled', 'false') + }) + + test('duplicate document', async ({page}, testInfo) => { + const title = `${TITLE_PREFIX} ${testInfo.title}` + const {studioPath} = await createDocument('documentActionsCI', title) + + await page.goto(studioPath) + + expect(page.locator(inputSelector)).toHaveValue(title) + page.locator('data-testid=action-menu-button').click() + + await Promise.all([ + page.waitForResponse(documentActionPath('duplicate')), + page.locator('data-testid=action-Duplicate').click(), + ]) + // Check if we are viewing a new document and not the one created for this test + await expect(page.url()).not.toMatch(`*${studioPath}`) + }) + + test('delete document', async ({page}, testInfo) => { + const title = `${TITLE_PREFIX} ${testInfo.title}` + const publishButton = page.locator('data-testid=action-Publish') + + const {studioPath, documentId} = await createDocument('documentActionsCI', title) + + await page.goto(studioPath) + + expect(page.locator(inputSelector)).toHaveValue(title) + page.locator('data-testid=action-menu-button').click() + page.locator('data-testid=action-Delete').click() + + await Promise.all([ + page.waitForResponse(documentActionPath('delete')), + page.locator('data-testid=confirm-delete').click(), + ]) + await expect(publishButton).toBeDisabled() + await expect(publishButton).toHaveAttribute('data-disabled', 'true') + expect( + await page.locator(`data-testid=pane-content >> data-testid=pane-item-${documentId}`).count() + ).toBe(0) + }) +})