Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 71d83d0

Browse files
committedDec 21, 2024·
WIP: Add Playwright tests
1 parent 6fdec25 commit 71d83d0

File tree

7 files changed

+413
-14
lines changed

7 files changed

+413
-14
lines changed
 

‎.github/workflows/playwright.yml

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Playwright Tests
2+
on: [pull_request, workflow_dispatch]
3+
jobs:
4+
test:
5+
timeout-minutes: 60
6+
runs-on: ubuntu-latest
7+
steps:
8+
- uses: actions/checkout@v4
9+
- uses: actions/setup-node@v4
10+
with:
11+
node-version: lts/*
12+
- name: Install dependencies
13+
run: npm ci
14+
- name: Install Playwright Browsers
15+
run: npx playwright install --with-deps
16+
- name: Run Playwright tests
17+
run: npx playwright test
18+
env:
19+
PASSWORD: ${{ secrets.PASSWORD }}
20+
- uses: actions/upload-artifact@v4
21+
if: ${{ !cancelled() }}
22+
with:
23+
name: playwright-report
24+
path: playwright-report/
25+
retention-days: 30

‎.gitignore

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
.DS_Store
2-
cypress.env.json
2+
33
node_modules/
4+
5+
cypress.env.json
46
cypress/downloads/
57
cypress/screenshots/
68
cypress/videos/
9+
10+
test-results/
11+
playwright-report/
12+
blob-report/
13+
playwright/.cache/

‎package-lock.json

+78-11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
"cy:open:prod": "cypress open --env environment=prod",
99
"test": "cypress run --record false --browser chrome",
1010
"test:prod": "cypress run --record false --browser chrome --env environment=prod",
11-
"test:prod:cloud": "cypress run --browser chrome --record --tag 'prod' --env environment=prod"
11+
"test:prod:cloud": "cypress run --browser chrome --record --tag 'prod' --env environment=prod",
12+
"pw:open": "PASSWORD='53cR37P@55w0rD' playwright test --ui"
1213
},
1314
"keywords": [
1415
"Cypress Playground",
@@ -20,8 +21,11 @@
2021
"author": "Walmyr <wlsf82@gmail.com> (https://walmyr.dev/)",
2122
"license": "MIT",
2223
"devDependencies": {
24+
"@playwright/test": "^1.49.1",
25+
"@types/node": "^22.10.2",
2326
"axe-core": "^4.10.2",
2427
"cypress": "^13.17.0",
25-
"cypress-axe": "^1.5.0"
28+
"cypress-axe": "^1.5.0",
29+
"lodash": "^4.17.21"
2630
}
2731
}

‎playwright.config.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// @ts-check
2+
const { defineConfig, devices } = require('@playwright/test')
3+
4+
/**
5+
* Read environment variables from file.
6+
* https://github.com/motdotla/dotenv
7+
*/
8+
// require('dotenv').config({ path: path.resolve(__dirname, '.env') })
9+
10+
/**
11+
* @see https://playwright.dev/docs/test-configuration
12+
*/
13+
module.exports = defineConfig({
14+
testDir: './tests',
15+
/* Run tests in files in parallel */
16+
fullyParallel: true,
17+
/* Fail the build on CI if you accidentally left test.only in the source code. */
18+
forbidOnly: !!process.env.CI,
19+
/* Retry on CI only */
20+
retries: process.env.CI ? 2 : 0,
21+
/* Opt out of parallel tests on CI. */
22+
workers: process.env.CI ? 1 : undefined,
23+
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
24+
reporter: 'html',
25+
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
26+
use: {
27+
/* Base URL to use in actions like `await page.goto('/')`. */
28+
// baseURL: 'http://127.0.0.1:3000',
29+
30+
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
31+
trace: 'on-first-retry',
32+
},
33+
34+
/* Configure projects for major browsers */
35+
projects: [
36+
{
37+
name: 'chromium',
38+
use: { ...devices['Desktop Chrome'] },
39+
},
40+
],
41+
})
42+

‎tests/example.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Hello, World!

‎tests/playground.spec.js

+253
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
const { test, expect } = require('@playwright/test')
2+
const { times } = require('lodash')
3+
4+
const fs = require('fs')
5+
const path = require('path')
6+
7+
test.beforeEach(async ({ page }) => {
8+
const date = new Date(Date.UTC(1982, 3, 15))
9+
await page.clock.setFixedTime(date)
10+
11+
// const environment = process.env.ENVIRONMENT
12+
// const url = environment === 'prod'
13+
// ? 'https://cypress-playground.s3.eu-central-1.amazonaws.com/index.html'
14+
// : './src/index.html'
15+
// await page.goto(url)
16+
await page.goto('https://cypress-playground.s3.eu-central-1.amazonaws.com/index.html')
17+
})
18+
19+
test.describe('Playwright Playground', () => {
20+
test('shows a promotional banner', async ({ page }) => {
21+
const banner = await page.locator('#promotional-banner')
22+
expect(await banner).toBeVisible()
23+
expect(await banner.innerText()).toContain('📣 Get to know the Cypress, from Zero to the Cloud course!')
24+
25+
const link = await banner.locator('a')
26+
expect(await link.getAttribute('target')).toEqual('_blank')
27+
expect(await link.getAttribute('href')).toEqual('https://www.udemy.com/course/cypress-from-zero-to-the-cloud/?referralCode=CABCDDFA5ADBB7BE2E1A')
28+
})
29+
30+
test('after visiting a page, asserts some text is visible', async ({ page }) => {
31+
const header = await page.locator('h1')
32+
expect(await header).toBeVisible()
33+
expect(await header.innerText()).toContain('Cypress Playground')
34+
35+
const subHeader = await page.locator('#visit h2')
36+
expect(await subHeader).toBeVisible()
37+
expect(await subHeader.innerText()).toEqual('cy.visit()')
38+
})
39+
40+
test('clicks a button and ensures an action is triggered', async ({ page }) => {
41+
const successMessage = await page.locator('#click span#success')
42+
await page.click('#click button')
43+
expect(await successMessage).toBeVisible()
44+
expect(await successMessage.innerText()).toEqual("You've been successfully subscribed to our newsletter.")
45+
46+
await page.clock.runFor(3000)
47+
expect(await successMessage).not.toBeVisible()
48+
})
49+
50+
test("types in an input which 'signs' a form, then asserts it's signed", async ({ page }) => {
51+
await page.fill('#type textarea', 'Mad Max')
52+
const signedText = await page.locator('#type em')
53+
expect(await signedText).toBeVisible()
54+
expect(await signedText.innerText()).toEqual('Mad Max')
55+
})
56+
57+
test('types in the signature, checks the checkbox to see the preview, then unchecks it', async ({ page }) => {
58+
await page.fill('#check textarea', 'Scarecrow')
59+
await page.check('#check input[type="checkbox"]')
60+
const preview = await page.locator('#check em')
61+
expect(await preview).toBeVisible()
62+
expect(await preview.innerText()).toEqual('Scarecrow')
63+
64+
await page.uncheck('#check input[type="checkbox"]')
65+
expect(await preview).not.toBeVisible()
66+
})
67+
68+
test('checks both radios and asserts if it is "on" or "off"', async ({ page }) => {
69+
const radioOn = await page.locator('#check-radio input[value="on"]')
70+
const radioOff = await page.locator('#check-radio input[value="off"]')
71+
72+
const onOffText = await page.locator('#check-radio strong p')
73+
expect(await onOffText).toBeVisible()
74+
expect(await onOffText.innerText()).toEqual('ON')
75+
76+
await radioOff.check()
77+
expect(await onOffText).toBeVisible()
78+
expect(await onOffText.innerText()).toEqual('OFF')
79+
80+
await radioOn.check()
81+
expect(await onOffText.innerText()).toEqual('ON')
82+
})
83+
84+
test('selects a type via the dropdown field and asserts on the selection', async ({ page }) => {
85+
await page.selectOption('#select select[name="selection-type"]', 'VIP')
86+
const selectedOption = await page.getByText("You've selected: VIP")
87+
expect(await selectedOption).toBeVisible()
88+
})
89+
90+
test('selects multiple fruits via the dropdown field and asserts on the selection', async ({ page }) => {
91+
await page.selectOption('#select select[multiple]', ['apple', 'banana', 'cherry'])
92+
const selectedFruits = await page.getByText("You've selected the following fruits: apple, banana, cherry")
93+
expect(await selectedFruits).toBeVisible()
94+
})
95+
96+
test('uploads a file and asserts the correct file name appears as a paragraph', async ({ page }) => {
97+
const fileInput = await page.locator('#select-file input[type="file"]')
98+
await fileInput.setInputFiles('cypress/fixtures/example.json')
99+
100+
const selectedFile = await page.getByText('The following file has been selected for upload: example.json')
101+
expect(await selectedFile).toBeVisible()
102+
})
103+
104+
test('clicks a button and triggers a request', async ({ page }) => {
105+
const button = await page.locator('#intercept button')
106+
await button.click()
107+
108+
const response = await page.waitForResponse('https://jsonplaceholder.typicode.com/todos/1')
109+
expect(response.status()).toEqual(200)
110+
111+
const jsonResponse = await response.json()
112+
expect(jsonResponse.id).toEqual(1)
113+
114+
const todoIdParagraph = await page.getByText('TODO ID: ')
115+
const titleParagraph = await page.getByText('Title: ')
116+
const completedParagraph = await page.getByText('Completed: ')
117+
const userIdParagraph = await page.getByText('User ID: ')
118+
119+
expect(await todoIdParagraph).toBeVisible()
120+
expect(await titleParagraph).toBeVisible()
121+
expect(await completedParagraph).toBeVisible()
122+
expect(await userIdParagraph).toBeVisible()
123+
})
124+
125+
test('clicks a button and triggers a stubbed request', async ({ page }) => {
126+
await page.route('**/todos/1', route => {
127+
route.fulfill({
128+
path: './cypress/fixtures/todo.json'
129+
})
130+
})
131+
132+
const button = await page.locator('#intercept button')
133+
await button.click()
134+
135+
const response = await page.waitForResponse('https://jsonplaceholder.typicode.com/todos/1')
136+
expect(await response.status()).toEqual(200)
137+
138+
// const jsonResponse = await response.json()
139+
// expect(await jsonResponse.id).toEqual(420)
140+
141+
const todoIdParagraph = await page.getByText('TODO ID: 420')
142+
const titleParagraph = await page.getByText('Title: Cypress test')
143+
const completedParagraph = await page.getByText('Completed: true')
144+
const userIdParagraph = await page.getByText('User ID: 66')
145+
146+
expect(await todoIdParagraph).toBeVisible()
147+
expect(await titleParagraph).toBeVisible()
148+
expect(await completedParagraph).toBeVisible()
149+
expect(await userIdParagraph).toBeVisible()
150+
})
151+
152+
test('clicks a button and simulates an API failure', async ({ page }) => {
153+
await page.route('**/todos/1', async route => {
154+
await route.fulfill({
155+
status: 500,
156+
})
157+
})
158+
159+
const button = await page.locator('#intercept button')
160+
await button.click()
161+
162+
const errorMessage = await page.locator('#intercept .error span')
163+
expect(await errorMessage).toBeVisible()
164+
expect(await errorMessage.innerText()).toEqual('Oops, something went wrong. Refresh the page and try again.')
165+
})
166+
167+
test('clicks a button and simulates a network failure', async ({ page }) => {
168+
await page.route('**/todos/1', route => route.abort())
169+
const button = await page.locator('#intercept button')
170+
await button.click()
171+
172+
const errorMessage = await page.locator('#intercept .error span')
173+
expect(await errorMessage).toBeVisible()
174+
expect(await errorMessage.innerText()).toEqual('Oops, something went wrong. Check your internet connection, refresh the page, and try again.')
175+
})
176+
177+
test('makes an HTTP request and asserts on the returned status code', async ({ page }) => {
178+
const response = await page.request.get('https://jsonplaceholder.typicode.com/todos/1')
179+
expect(response.status()).toEqual(200)
180+
})
181+
182+
times(10, index => {
183+
test(`selects ${index + 1} out of 10`, async ({ page }) => {
184+
await page.fill('#input-range input[type="range"]', `${index + 1}`)
185+
const selectedLevel = await page.getByText(`You're on level: ${index + 1}`)
186+
expect(await selectedLevel).toBeVisible()
187+
})
188+
})
189+
190+
test('selects a date and asserts the correct date has been displayed', async ({ page }) => {
191+
await page.fill('#input-date input[type="date"]', '2024-01-16')
192+
193+
const dateParagraph = await page.locator('#input-date p#date-paragraph')
194+
expect(await dateParagraph).toBeVisible()
195+
expect(await dateParagraph.innerText()).toEqual("The date you've selected is: 2024-01-16")
196+
})
197+
198+
test('types a password without leaking it, shows it, and hides it again', async ({ page }) => {
199+
await page.fill('#password-input input[type="password"]', process.env.PASSWORD)
200+
201+
const showPasswordCheckbox = await page.locator('#password-input input[type="checkbox"]')
202+
await showPasswordCheckbox.check()
203+
204+
const textInput = await page.locator('#password-input input[type="text"]')
205+
expect(await textInput).toBeVisible()
206+
expect(await textInput.inputValue()).toEqual(process.env.PASSWORD)
207+
208+
await showPasswordCheckbox.uncheck()
209+
expect(await textInput).not.toBeVisible()
210+
})
211+
212+
test('counts the number of animals in a list', async ({ page }) => {
213+
const animals = await page.$$('#should-have-length ul li')
214+
expect(animals.length).toEqual(5)
215+
})
216+
217+
test('freezes the browser clock and asserts the frozen date is displayed', async ({ page }) => {
218+
const dateSection = await page.locator('#date-section #date-section-paragraph')
219+
expect(await dateSection).toBeVisible()
220+
expect(await dateSection.innerText()).toEqual('Current date: 1982-04-15')
221+
})
222+
223+
test('copies the code, types it, submits it, then asserts on the success message', async ({ page }) => {
224+
const codeElement = await page.locator('#copy-paste span#timestamp')
225+
const code = await codeElement.innerText()
226+
227+
const codeField = await page.locator('#copy-paste input[type="number"]')
228+
await codeField.fill(code)
229+
230+
const submitCodeButton = await page.locator('#copy-paste button')
231+
await submitCodeButton.click()
232+
233+
const successCodeSubmissionMessage = await page.getByText("Congrats! You've entered the correct code.")
234+
expect(await successCodeSubmissionMessage).toBeVisible()
235+
236+
await page.clock.runFor(3000)
237+
expect(await successCodeSubmissionMessage).not.toBeVisible()
238+
})
239+
240+
test('downloads a file, reads it, and asserts on its content', async ({ page }) => {
241+
const downloadPromise = page.waitForEvent('download')
242+
const filePath = path.join(__dirname, 'example.txt')
243+
244+
const downloadLink = await page.getByText('Download a text file').last()
245+
await downloadLink.click()
246+
const download = await downloadPromise
247+
await download.saveAs(`tests/${download.suggestedFilename()}`)
248+
249+
const fileContent = fs.readFileSync(filePath, 'utf8')
250+
251+
expect(fileContent).toEqual('Hello, World!')
252+
})
253+
})

0 commit comments

Comments
 (0)
Please sign in to comment.