Skip to content

Commit

Permalink
tests/add_playwright (#55)
Browse files Browse the repository at this point in the history
* chore: playwright init

* write some basic tests

* test: writing invalid value

* improve writeInMonaco function

* add gh workflow for playwright

* make zod version test a bit smarter

* chore: regenerate lock

* chore: update vscode settings

* feat: provide fixture for reading content within monaco editors

* fix: switch to getByRole to have a locator based on accessibility attribs

* Update playwright config

* Improve fixture management

* Fix types

* avoid NODEJS types

* fix wrong nth for code editor fixtures

---------

Co-authored-by: Marco Ilari <marilari88@gmail.com>
  • Loading branch information
giacomocerquone and marilari88 committed Aug 12, 2024
1 parent bbc1338 commit 606ef5f
Show file tree
Hide file tree
Showing 9 changed files with 313 additions and 2 deletions.
27 changes: 27 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@ dist-ssr
*.njsproj
*.sln
*.sw?
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
{
"editor.defaultFormatter": "biomejs.biome"
"editor.defaultFormatter": "biomejs.biome",
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
}
}
82 changes: 82 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"build": "tsc && vite build",
"preview": "vite preview",
"types": "tsc --noEmit",
"e2e": "playwright test",
"e2e:ui": "playwright test --ui",
"biome:ci": "biome ci",
"biome:check": "biome check",
"biome:fix": "biome check --fix --unsafe"
Expand All @@ -24,6 +26,8 @@
"react-icons": "^5.2.0"
},
"devDependencies": {
"@playwright/test": "^1.46.0",
"@types/node": "^22.1.0",
"@biomejs/biome": "1.8.3",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
Expand Down
35 changes: 35 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {defineConfig, devices} from '@playwright/test'

/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './tests',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:5173/',
trace: 'on-first-retry',
},

projects: [
{
name: 'chromium',
use: {...devices['Desktop Chrome']},
},

{
name: 'firefox',
use: {...devices['Desktop Firefox']},
},
],

webServer: {
command: 'npm run dev',
url: 'http://localhost:5173/',
reuseExistingServer: !process.env.CI,
},
})
2 changes: 1 addition & 1 deletion src/hooks/usePersistAppData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {STORAGE_KEY} from '../constants'
import type {AppData} from '../utils/appData'

export function usePersistAppData(data: AppData) {
const timeoutRef = useRef<number | null>(null)
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)

useEffect(() => {
const saveData = () => {
Expand Down
108 changes: 108 additions & 0 deletions tests/fixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import type {Locator, Page} from '@playwright/test'
import {test as base} from '@playwright/test'

export const test = base.extend<{
codeEditors: CodeEditors
}>({
codeEditors: async ({page}, use) => {
const codeEditors = new CodeEditors(page)
await use(codeEditors)
},
page: async ({baseURL, page}, use) => {
if (!baseURL) throw new Error('baseURL is required')
await page.goto(baseURL)
await use(page)
},
})

export class CodeEditors {
private readonly codeEditors: Locator

constructor(public readonly page: Page) {
this.codeEditors = this.page.getByRole('code')
}

async getSchemaEditorContent({formatOutput = true}: {formatOutput?: boolean} = {}) {
return await getMonacoContent({locator: this.codeEditors.first(), formatOutput})
}

async getValueEditorsContent({
nth = 0,
formatOutput = true,
}: {nth?: number; formatOutput?: boolean} = {}) {
return await getMonacoContent({locator: this.codeEditors.nth(nth + 1), formatOutput})
}

async writeSchema({
text,
replacePreviousContent = true,
}: {text: string; replacePreviousContent?: boolean}) {
await writeInMonaco({
locator: this.codeEditors.first(),
page: this.page,
text,
replacePreviousContent,
})
}

async writeValue({
nth = 0,
text,
replacePreviousContent = true,
}: {nth?: number; text: string; replacePreviousContent?: boolean}) {
await writeInMonaco({
locator: this.codeEditors.nth(nth + 1),
page: this.page,
text,
replacePreviousContent,
})
}
}

/**
* This function can be used to write some content within a monaco editor
*
* @param {Object} params - The parameters object
* @param {Locator} params.locator - The locator for the editor element
* @param {Page} params.page - The Playwright page object
* @param {string} params.text - The text to write inside the editor
* @param {boolean} [params.replacePreviousContent=true] - Optionally delete the existing content in the editor. Defaults to true
*/
const writeInMonaco = async ({
locator,
page,
text,
replacePreviousContent,
}: {
locator: Locator
page: Page
text: string
replacePreviousContent?: boolean
}) => {
await locator.click()
if (replacePreviousContent) {
await page.keyboard.press('ControlOrMeta+KeyA')
}
await page.keyboard.type(text)
}

/**
* This function can be used to retrieve the full content within a monaco editor
*
* @param {Object} params - The parameters object
* @param {Locator} params.locator - The locator for the editor element
* @param {Page} params.page - The playwright page object
* @param {boolean} [params.formatOutput=true] - Optionally clean the string to have a more reliable way of asserting equalities. Defaults to true
*/
export const getMonacoContent = async ({
locator,
formatOutput = true,
}: {locator: Locator; formatOutput?: boolean}) => {
const value = await locator.textContent()

if (formatOutput) {
return value?.replace(/\s+/g, '')
}

return value
}
48 changes: 48 additions & 0 deletions tests/tests.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import {expect} from '@playwright/test'

import * as zod from '../src/zod'
import {test} from './fixtures'

test('has title "Zod Playground', async ({page}) => {
await expect(page).toHaveTitle(/Zod Playground/)
})

test('has header with title, share, theme toggler and github repo link', async ({page}) => {
await expect(page.getByText('Zod Playground')).toBeVisible()
await expect(page.getByRole('button', {name: 'Share'})).toBeVisible()
await expect(page.getByLabel('Toggle color scheme')).toBeVisible()
await expect(page.getByRole('banner').getByRole('link')).toHaveAttribute(
'href',
'https://github.com/marilari88/zod-playground',
)
})

test('zod version switch', async ({page}) => {
const latestZodVersion = (await zod.getVersions('latest'))[0]
const anotherZodVersion = (await zod.getVersions()).find(
(zVersion) => zVersion !== latestZodVersion,
)

await page.getByRole('button', {name: `v${latestZodVersion}`}).click()
await page.getByRole('option', {name: anotherZodVersion}).click()

await expect(page.getByRole('button', {name: `v${latestZodVersion}`})).not.toBeVisible()
await expect(page.getByRole('button', {name: `v${anotherZodVersion}`})).toBeVisible()
})

test('has default schema', async ({codeEditors}) => {
const editorValue = await codeEditors.getSchemaEditorContent()

expect(editorValue).toEqual('1234z.object({name:z.string(),birth_year:z.number().optional()})')
})

test('has invalid marker when an invalid value is in the Value Editor', async ({
page,
codeEditors,
}) => {
await codeEditors.writeValue({
text: 'Invalid value',
})

await expect(page.locator('div').filter({hasText: /^Invalid$/})).toBeVisible()
})

0 comments on commit 606ef5f

Please sign in to comment.