From b57c07f0d3f59cff1b07d8d42a65499cdc865aaa Mon Sep 17 00:00:00 2001 From: Tommy Petty Date: Fri, 29 Sep 2023 15:52:42 -0400 Subject: [PATCH] Prep work for making e2e more isolated --- test/e2e/README.md | 2 +- test/e2e/helpers/createUniqueDocument.ts | 14 +- test/e2e/helpers/sanityClient.ts | 44 +++++-- test/e2e/tests/desk/inspectors.spec.ts | 157 ++++++++++++----------- test/e2e/tests/inputs/text.spec.ts | 135 +++++++++---------- 5 files changed, 182 insertions(+), 170 deletions(-) diff --git a/test/e2e/README.md b/test/e2e/README.md index 5ac0737027c..5beb31a2337 100644 --- a/test/e2e/README.md +++ b/test/e2e/README.md @@ -1,6 +1,6 @@ # E2E Testing in the Studio -Before you get started with writing and running tests, you need to get hold of a token - either using your own Sanity user token (`sanity debug --secrets` will give you the CLI token), or by creating a project API token using https://sanity.io/manage. +Before you get started with writing and running tests, you need to get hold of a token - either using your own Sanity user token (`sanity debug --secrets` will give you the CLI token provided you are logged in `sanity login`), or by creating a project API token using https://sanity.io/manage. The tests expects to find the token in an environment variable named `SANITY_E2E_SESSION_TOKEN`. Either define it in your shell, or add it to the `.env.local` file in the repository root. diff --git a/test/e2e/helpers/createUniqueDocument.ts b/test/e2e/helpers/createUniqueDocument.ts index 881e2a61b38..7235f4040ab 100644 --- a/test/e2e/helpers/createUniqueDocument.ts +++ b/test/e2e/helpers/createUniqueDocument.ts @@ -1,18 +1,16 @@ -import {SanityDocument, SanityDocumentStub} from '@sanity/client' +import {SanityClient, SanityDocument, SanityDocumentStub} from '@sanity/client' import {uuid} from '@sanity/uuid' -import {testSanityClient} from './sanityClient' -export async function createUniqueDocument({ - _type, - _id, - ...restProps -}: SanityDocumentStub): Promise> { +export async function createUniqueDocument( + client: SanityClient, + {_type, _id, ...restProps}: SanityDocumentStub, +): Promise> { const doc = { _type, _id: _id || uuid(), ...restProps, } - await testSanityClient.create(doc, {visibility: 'async'}) + await client.create(doc, {visibility: 'async'}) return doc } diff --git a/test/e2e/helpers/sanityClient.ts b/test/e2e/helpers/sanityClient.ts index 4b036c18d2b..79de448a044 100644 --- a/test/e2e/helpers/sanityClient.ts +++ b/test/e2e/helpers/sanityClient.ts @@ -1,8 +1,33 @@ import {createClient} from '@sanity/client' +import {SanityClient} from 'sanity' import {SANITY_E2E_SESSION_TOKEN} from '../env' -import {STALE_TEST_THRESHOLD_MS, STUDIO_DATASET_NAME, STUDIO_PROJECT_ID} from './constants' +import {STUDIO_DATASET_NAME, STUDIO_PROJECT_ID} from './constants' +import {uuid} from '@sanity/uuid' -export const testSanityClient = createClient({ +export class TestContext { + client: SanityClient + + constructor(client: SanityClient) { + this.client = client + } + + documentIds = new Set() + + getUniqueDocumentId() { + const documentId = uuid() + this.documentIds.add(documentId) + return documentId + } + + teardown() { + this.client.delete({ + query: '*[_id in $ids]', + params: {ids: [...this.documentIds].map((id) => `drafts.${id}`)}, + }) + } +} + +const testSanityClient = createClient({ projectId: STUDIO_PROJECT_ID, dataset: STUDIO_DATASET_NAME, token: SANITY_E2E_SESSION_TOKEN, @@ -10,13 +35,10 @@ export const testSanityClient = createClient({ 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}, - } +/* eslint-disable callback-return*/ +export function withDefaultClient(callback: (context: TestContext) => void): void { + const context = new TestContext(testSanityClient) + callback(context) + + context.teardown() } diff --git a/test/e2e/tests/desk/inspectors.spec.ts b/test/e2e/tests/desk/inspectors.spec.ts index c69fc21d7a6..7067d1c13ba 100644 --- a/test/e2e/tests/desk/inspectors.spec.ts +++ b/test/e2e/tests/desk/inspectors.spec.ts @@ -1,85 +1,90 @@ +/* eslint-disable max-nested-callbacks */ import {test, expect} from '@playwright/test' -import {createUniqueDocument, testSanityClient} from '../../helpers' - -test.describe.skip('sanity/desk: document inspectors', () => { - test('open and close custom inspector', async ({page}) => { - await page.goto('/test/content/input-debug;inspectorsTest;inspectors-test') - - // Click to open inspector - await page - .locator('[data-ui="StatusButton"][aria-label="Custom inspector"]') - .click({timeout: 0}) - - // Expect button to be selected and inspector to be visible - await expect( - page.locator('[data-ui="StatusButton"][aria-label="Custom inspector"][data-selected]'), - ).toBeVisible() - await expect(page.locator('aside[data-ui="DocumentInspectorPanel"]')).toBeVisible() - await expect(page.locator('aside[data-ui="DocumentInspectorPanel"] h1')).toContainText( - 'Custom inspector', - ) - - // Click to close inspector - await page.locator('button[aria-label="Close custom inspector"]').click() - - expect( - await page - .locator('[data-ui="StatusButton"][aria-label="Custom inspector"]') - .evaluate((el) => el.getAttribute('data-selected')), - ).toBe(null) - }) +import {createUniqueDocument, withDefaultClient} from '../../helpers' - test('open "Validation" inspector', async ({page}) => { - // create published document - const uniqueDoc = await createUniqueDocument({_type: 'validationTest'}) - const id = uniqueDoc._id! +withDefaultClient((context) => { + test.describe.skip('sanity/desk: document inspectors', () => { + test('open and close custom inspector', async ({page}) => { + await page.goto('/test/content/input-debug;inspectorsTest;inspectors-test') - // create draft document - await createUniqueDocument({ - _type: 'inspectorsTest', - _id: `drafts.${id}`, - name: 'Edited by e2e test runner', + // Click to open inspector + await page + .locator('[data-ui="StatusButton"][aria-label="Custom inspector"]') + .click({timeout: 0}) + + // Expect button to be selected and inspector to be visible + await expect( + page.locator('[data-ui="StatusButton"][aria-label="Custom inspector"][data-selected]'), + ).toBeVisible() + await expect(page.locator('aside[data-ui="DocumentInspectorPanel"]')).toBeVisible() + await expect(page.locator('aside[data-ui="DocumentInspectorPanel"] h1')).toContainText( + 'Custom inspector', + ) + + // Click to close inspector + await page.locator('button[aria-label="Close custom inspector"]').click() + + expect( + await page + .locator('[data-ui="StatusButton"][aria-label="Custom inspector"]') + .evaluate((el) => el.getAttribute('data-selected')), + ).toBe(null) }) - await page.goto(`/test/content/input-debug;validationTest;${id}`) - - // Click to open inspector - await page.locator('[data-ui="StatusButton"][aria-label="Validation"]').click({timeout: 0}) - - // Expect button to be selected and inspector to be visible - await expect( - page.locator('[data-ui="StatusButton"][aria-label="Validation"][data-selected]'), - ).toBeVisible() - await expect(page.locator('aside[data-ui="DocumentInspectorPanel"]')).toBeVisible() - await expect(page.locator('aside[data-ui="DocumentInspectorPanel"] h1')).toContainText( - 'Validation', - ) - }) - - test('open "Review changes" inspector', async ({page}) => { - // create published document - const uniqueDoc = await createUniqueDocument({_type: 'inspectorsTest'}) - const id = uniqueDoc._id! - - // create draft document - await createUniqueDocument({ - _type: 'inspectorsTest', - _id: `drafts.${id}`, - name: 'Edited by e2e test runner', + test('open "Validation" inspector', async ({page}) => { + // create published document + const uniqueDoc = await createUniqueDocument(context.client, {_type: 'validationTest'}) + const id = uniqueDoc._id! + + // create draft document + await createUniqueDocument(context.client, { + _type: 'inspectorsTest', + _id: `drafts.${id}`, + name: 'Edited by e2e test runner', + }) + + await page.goto(`/test/content/input-debug;validationTest;${id}`) + + // Click to open inspector + await page.locator('[data-ui="StatusButton"][aria-label="Validation"]').click({timeout: 0}) + + // Expect button to be selected and inspector to be visible + await expect( + page.locator('[data-ui="StatusButton"][aria-label="Validation"][data-selected]'), + ).toBeVisible() + await expect(page.locator('aside[data-ui="DocumentInspectorPanel"]')).toBeVisible() + await expect(page.locator('aside[data-ui="DocumentInspectorPanel"] h1')).toContainText( + 'Validation', + ) }) - await page.goto(`/test/content/input-debug;inspectorsTest;${id}`) - - // Click to open inspector - await page.locator('[data-testid="review-changes-button"]').click() - - // Expect button to be selected and inspector to be visible - await expect(page.locator('[data-testid="review-changes-button"][data-selected]')).toBeVisible() - await expect(page.locator('aside[data-ui="DocumentInspectorPanel"]')).toBeVisible() - await expect(page.locator('aside[data-ui="DocumentInspectorPanel"] h1')).toContainText( - 'Review changes', - ) - - await testSanityClient.delete(id) + test('open "Review changes" inspector', async ({page}) => { + // create published document + const uniqueDoc = await createUniqueDocument(context.client, {_type: 'inspectorsTest'}) + const id = uniqueDoc._id! + + // create draft document + await createUniqueDocument(context.client, { + _type: 'inspectorsTest', + _id: `drafts.${id}`, + name: 'Edited by e2e test runner', + }) + + await page.goto(`/test/content/input-debug;inspectorsTest;${id}`) + + // Click to open inspector + await page.locator('[data-testid="review-changes-button"]').click() + + // Expect button to be selected and inspector to be visible + await expect( + page.locator('[data-testid="review-changes-button"][data-selected]'), + ).toBeVisible() + await expect(page.locator('aside[data-ui="DocumentInspectorPanel"]')).toBeVisible() + await expect(page.locator('aside[data-ui="DocumentInspectorPanel"] h1')).toContainText( + 'Review changes', + ) + + await context.client.delete(id) + }) }) }) diff --git a/test/e2e/tests/inputs/text.spec.ts b/test/e2e/tests/inputs/text.spec.ts index 6a43833dc64..450fc49ebf2 100644 --- a/test/e2e/tests/inputs/text.spec.ts +++ b/test/e2e/tests/inputs/text.spec.ts @@ -1,18 +1,10 @@ +/* eslint-disable max-nested-callbacks */ /** * TODO: This need to refactored once we have a better e2e framework/setup in place. * Makeshift code to reproduce a specific bug. */ import {test, expect} from '@playwright/test' -import {uuid} from '@sanity/uuid' -import {testSanityClient} from '../../helpers' - -const documentIds = new Set() - -function getUniqueDocumentId() { - const documentId = uuid() - documentIds.add(documentId) - return documentId -} +import {withDefaultClient} from '../../helpers' const kanji = ` 速ヒマヤレ誌相ルなあね日諸せ変評ホ真攻同潔ク作先た員勝どそ際接レゅ自17浅ッ実情スヤ籍認ス重力務鳥の。8平はートご多乗12青國暮整ル通国うれけこ能新ロコラハ元横ミ休探ミソ梓批ざょにね薬展むい本隣ば禁抗ワアミ部真えくト提知週むすほ。査ル人形ルおじつ政謙減セヲモ読見れレぞえ録精てざ定第ぐゆとス務接産ヤ写馬エモス聞氏サヘマ有午ごね客岡ヘロ修彩枝雨父のけリド。 @@ -20,70 +12,65 @@ const kanji = ` 住ゅなぜ日16語約セヤチ任政崎ソオユ枠体ぞン古91一専泉給12関モリレネ解透ぴゃラぼ転地す球北ドざう記番重投ぼづ。期ゃ更緒リだすし夫内オ代他られくド潤刊本クヘフ伊一ウムニヘ感週け出入ば勇起ょ関図ぜ覧説めわぶ室訪おがト強車傾町コ本喰杜椿榎ほれた。暮る生的更芸窓どさはむ近問ラ入必ラニス療心コウ怒応りめけひ載総ア北吾ヌイヘ主最ニ余記エツヤ州5念稼め化浮ヌリ済毎養ぜぼ。 `.trim() -test.describe('inputs: text', () => { - test.slow() // Because of waiting for mutations, remote values etc - - test('correctly applies kanji edits', async ({page}) => { - const documentId = getUniqueDocumentId() - await page.goto(`/test/content/input-ci;textsTest;${documentId}`) - - function getRemoteValue() { - return testSanityClient - .getDocument(`drafts.${documentId}`) - .then((doc) => (doc ? doc.simple : null)) - } - - await page.waitForSelector('data-testid=field-simple', {timeout: 30000}) - const field = page.getByTestId('field-simple').getByRole('textbox') - - // Enter initial text and wait for the mutate call to be sent - const response = page.waitForResponse(/mutate/) - await field.fill(kanji) - await response - - // Expect the document to now have the base value - let currentExpectedValue = kanji - expect(await field.inputValue()).toBe(currentExpectedValue) - expect(await getRemoteValue()).toBe(currentExpectedValue) - - // Edit the value to start with "Paragraph 1: " - const p1Prefix = 'Paragraph 1: ' - let nextExpectedValue = `${p1Prefix}${kanji}` - await field.fill(nextExpectedValue) - await page.waitForTimeout(1000) // Hack, we need to wait for the mutation to be received - - // Expect both the browser input and the document to now have the updated value - currentExpectedValue = `${p1Prefix}${kanji}` - expect(await field.inputValue()).toBe(currentExpectedValue) - expect(await getRemoteValue()).toBe(currentExpectedValue) - - // Now move to the end of the paragraph and add a suffix - const p1Suffix = ' (end of paragraph 1)' - nextExpectedValue = currentExpectedValue.replace(/\n\n/, `${p1Suffix}\n\n`) - await field.fill(nextExpectedValue) - await page.waitForTimeout(1000) // Hack, we need to wait for the mutation to be received - - // Expect both the browser input and the document to now have the updated value - currentExpectedValue = nextExpectedValue - expect(await field.inputValue()).toBe(currentExpectedValue) - expect(await getRemoteValue()).toBe(currentExpectedValue) - - // Move to the end of the field and add a final suffix - const p2Suffix = `. EOL.` - nextExpectedValue = `${currentExpectedValue}${p2Suffix}` - await field.fill(nextExpectedValue) - await page.waitForTimeout(1000) // Hack, we need to wait for the mutation to be received - - // Expect both the browser input and the document to now have the updated value - currentExpectedValue = nextExpectedValue - expect(await field.inputValue()).toBe(currentExpectedValue) - expect(await getRemoteValue()).toBe(currentExpectedValue) - }) - - test.afterAll(async () => { - await testSanityClient.delete({ - query: '*[_id in $ids]', - params: {ids: [...documentIds].map((id) => `drafts.${id}`)}, +withDefaultClient((context) => { + test.describe('inputs: text', () => { + test.slow() // Because of waiting for mutations, remote values etc + + test('correctly applies kanji edits', async ({page}) => { + const documentId = context.getUniqueDocumentId() + await page.goto(`/test/content/input-ci;textsTest;${documentId}`) + + function getRemoteValue() { + return context.client + .getDocument(`drafts.${documentId}`) + .then((doc) => (doc ? doc.simple : null)) + } + + await page.waitForSelector('data-testid=field-simple', {timeout: 30000}) + const field = page.getByTestId('field-simple').getByRole('textbox') + + // Enter initial text and wait for the mutate call to be sent + const response = page.waitForResponse(/mutate/) + await field.fill(kanji) + await response + + // Expect the document to now have the base value + let currentExpectedValue = kanji + expect(await field.inputValue()).toBe(currentExpectedValue) + expect(await getRemoteValue()).toBe(currentExpectedValue) + + // Edit the value to start with "Paragraph 1: " + const p1Prefix = 'Paragraph 1: ' + let nextExpectedValue = `${p1Prefix}${kanji}` + await field.fill(nextExpectedValue) + await page.waitForTimeout(1000) // Hack, we need to wait for the mutation to be received + + // Expect both the browser input and the document to now have the updated value + currentExpectedValue = `${p1Prefix}${kanji}` + expect(await field.inputValue()).toBe(currentExpectedValue) + expect(await getRemoteValue()).toBe(currentExpectedValue) + + // Now move to the end of the paragraph and add a suffix + const p1Suffix = ' (end of paragraph 1)' + nextExpectedValue = currentExpectedValue.replace(/\n\n/, `${p1Suffix}\n\n`) + await field.fill(nextExpectedValue) + await page.waitForTimeout(1000) // Hack, we need to wait for the mutation to be received + + // Expect both the browser input and the document to now have the updated value + currentExpectedValue = nextExpectedValue + expect(await field.inputValue()).toBe(currentExpectedValue) + expect(await getRemoteValue()).toBe(currentExpectedValue) + + // Move to the end of the field and add a final suffix + const p2Suffix = `. EOL.` + nextExpectedValue = `${currentExpectedValue}${p2Suffix}` + await field.fill(nextExpectedValue) + await page.waitForTimeout(1000) // Hack, we need to wait for the mutation to be received + + // Expect both the browser input and the document to now have the updated value + currentExpectedValue = nextExpectedValue + expect(await field.inputValue()).toBe(currentExpectedValue) + expect(await getRemoteValue()).toBe(currentExpectedValue) }) }) })