cypressๆต‹่ฏ•ๆก†ๆžถๅŸบ็ก€็”จๆณ•

E2Eๅบ“ Cypress

็ซฏๅˆฐ็ซฏEnd to End (E2E)ๆต‹่ฏ•๏ผŒๅฐ†็ณป็ปŸไฝœไธบไธ€ไธชๆ•ดไฝ“็š„ๆต‹่ฏ•ๆ–นๆณ•

ๅ‡†ๅค‡ๅทฅไฝœ

้ฆ–ๅ…ˆๆœ‰ไธ€ไธชๅ‰็ซฏ้กน็›ฎ็š„ๆ‰“ๅŒ…ๆ–‡ไปถ๏ผŒๅฏไปฅๅฏๅŠจ็š„ๅŽ็ซฏๆœๅŠก๏ผŒๆฅๅš่‡ชๅŠจๅŒ–e2eๆต‹่ฏ•

ๅฎ‰่ฃ…้…็ฝฎ

1
npm install --save-dev cypress # ๅฐ† Cypress ๅฎ‰่ฃ…ๅˆฐๅ‰็ซฏ ๏ผŒไฝœไธบๅผ€ๅ‘ไพ่ต–้กน

้…็ฝฎpackage.json ๆทปๅŠ ่„šๆœฌ cypress:open ๅ’Œ test:e2e

1
2
3
4
5
6
7
8
9
10
11
12
13
{
// ..
"scripts": {
"start": "webpack-dev-server --open --mode development",
"start-prod": "node app.js",
"test": "jest",
"eslint": "eslint './**/*.{js,jsx}'",
"cypress:open": "cypress open",
"test:e2e": "cypress run",
"build": "webpack --mode production"
},
// ..
}

ๅŽ็ซฏๆœๅŠกไนŸๅฏไปฅๆทปๅŠ ๅฏๅŠจๆŒ‡ไปค "start-prod": "node app.js"

app.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
const express = require('express')
const app = express()

// get the port from env variable
const PORT = process.env.PORT || 5001

app.use(express.static('dist'))

app.listen(PORT, () => {
// eslint-disable-next-line
console.log(`server started on port ${PORT}`, `http://localhost:${PORT}`)
})

ps: ๅฏนไบŽ const express = require('express')็š„eslintๆŠฅ้”™๏ผŒๅฏไปฅๅœจ.eslintrc.jsไธญๆทปๅŠ ้…็ฝฎ

1
2
3
4
5
6

'env': {
// ..
'node': true,
'commonjs': true,
},

.gitignore ไนŸๅฏๆŠŠ่ง†้ข‘ๆทปๅŠ ๅฟฝ็•ฅ cypress/videos

้ฆ–ๆฌกๅฏๅŠจ

้ฆ–ๆฌกๅฏๅŠจcypress ้กน็›ฎๆ น็›ฎๅฝ•ไผšๅคšไธ€ไธช cypress ๆ–‡ไปถๅคนๅ’Œไธ€ไธช้…็ฝฎๆ–‡ไปถ cypress.config.js

1
npm run cypress:open

cypress.config.js:

1
2
3
4
5
6
7
8
9
10
11
12
// cypress.config.js
const { defineConfig } = require('cypress')

module.exports = defineConfig({
e2e: {
// eslint-disable-next-line
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
})

็›ฎๅฝ•็ป“ๆž„:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
โžœ  my-proj git:(main) โœ— tree -L 2 
.
โ”œโ”€โ”€ README.md
โ”œโ”€โ”€ app.js
โ”œโ”€โ”€ cypress
โ”‚ โ”œโ”€โ”€ e2e
โ”‚ โ”œโ”€โ”€ fixtures
โ”‚ โ”œโ”€โ”€ screenshots
โ”‚ โ””โ”€โ”€ support
โ”œโ”€โ”€ cypress.config.js
โ”œโ”€โ”€ dist
โ”‚ โ”œโ”€โ”€ bundle.js
โ”‚ โ”œโ”€โ”€ bundle.js.LICENSE.txt
โ”‚ โ””โ”€โ”€ index.html
# ..

็ฌฌไธ€ๆฌกCypressไผšๅœจ integration/examples ็›ฎๅฝ•ไธญๅˆ›ๅปบๆต‹่ฏ•็คบไพ‹ ๅฏไปฅๅˆ ไบ†examplesๆ–‡ไปถๅคน

cy ๅฏผ่‡ด็š„ eslint ้ฃŽๆ ผๆŠฅ้”™

ๆŠฅ้”™ๅคงๆฆ‚่ฟ™ๆ ท

1
2
3
4
5
6
7
8
9
10
11
12
13
 *  ๆญฃๅœจๆ‰ง่กŒไปปๅŠก: npm run eslint 


> my-proj@1.0.0 eslint
> eslint './**/*.{js,jsx}'

Warning: React version not specified in eslint-plugin-react settings. See https://github.com/jsx-eslint/eslint-plugin-react#configuration .

/Users/weimo/mine/code/my-proj/cypress/e2e/app.cy.js
3:5 error 'cy' is not defined no-undef
4:5 error 'cy' is not defined no-undef
5:5 error 'cy' is not defined no-undef
# ..

ๆˆ‘ไปฌๅฏไปฅ้€š่ฟ‡ๅฎ‰่ฃ…eslint-plugin-cypressไฝœไธบๅผ€ๅ‘ไพ่ต–้กนๆฅๆ‘†่„ฑ่ฟ™ไธชๆŠฅ้”™

1
npm install eslint-plugin-cypress --save-dev

ๆ”นๅ˜ .eslintrc.jsไธญ็š„้…็ฝฎ (vscodeๅฎ‰่ฃ…ไบ†eslintๆ‹“ๅฑ•้œ€่ฆไฟฎๆ”นsetting.json)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
module.exports = {
"env": {
"browser": true,
"es6": true,
"jest/globals": true,
"cypress/globals": true
},
"extends": [
// ...
],
"parserOptions": {
// ...
},
"plugins": [
"react", "jest", "cypress"
],
"rules": {
// ...
}
}

็ผ–ๅ†™่ฟ่กŒๆต‹่ฏ•

ๆทปๅŠ ๆ–‡ไปถ cypress/e2e/app.cy.js:

1
2
3
4
5
6
7
describe('Pokedex', function() {
it('front page can be opened ไธป้กตๅบ”่ฏฅ่ƒฝๆ‰“ๅผ€', function() {
cy.visit('http://localhost:5001')
cy.contains('ivysaur') // ไปฃ็ ๆ˜ฏๅ…จๅฐๅ†™็š„๏ผŒ้กต้ข็”ฑcssๆŽงๅˆถๆ˜พ็คบไธบ้ฆ–ๅญ—ๅคงๅ†™๏ผŒๆ‰€ไปฅ่ฟ™้‡Œๅฐๅ†™
cy.contains('Pokรฉmon and Pokรฉmon character names are trademarks of Nintendo.')
})
})

ๅฏๅŠจๆต‹่ฏ•

ๅฏๅŠจๆœๅŠก ่ฟ่กŒ็›ฎๅ‰็š„ๆต‹่ฏ•ๆ˜ฏๆต‹่ฏ•5001็ซฏๅฃ

ๅฏไปฅ่ฟ่กŒ npm run cypress:open ๅŽไฝฟ็”จไบคไบ’็•Œ้ข็œ‹็ป“ๆžœ:

Cypress demo

ไนŸๅฏไปฅ่ฟ่กŒ npm run test:e2e ๅŽๅœจๆŽงๅˆถๅฐ็œ‹ๅˆฐ็ป“ๆžœ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
ๆญฃๅœจๆ‰ง่กŒไปปๅŠก: npm run test:e2e 


> my-proj@1.0.0 test:e2e
> cypress run


DevTools listening on ws://127.0.0.1:55305/devtools/browser/25c805c0-5b4b-4250-9c9e-6cc43f8184a8
2024-06-20 17:58:23.038 Cypress[4588:4056456] WARNING: Secure coding is not enabled for restorable state! Enable secure coding by implementing NSApplicationDelegate.applicationSupportsSecureRestorableState: and returning YES.
Couldn't find tsconfig.json. tsconfig-paths will be skipped

====================================================================================================

(Run Starting)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Cypress: 13.12.0 โ”‚
โ”‚ Browser: Electron 118 (headless) โ”‚
โ”‚ Node Version: v16.20.2 (/Users/weimo/.nvm/versions/node/v16.20.2/bin/node) โ”‚
โ”‚ Specs: 1 found (app.cy.js) โ”‚
โ”‚ Searched: cypress/e2e/**/*.cy.{js,jsx,ts,tsx} โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜


โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

Running: app.cy.js (1 of 1)


Pokedex
1) front page can be opened ไธป้กตๅบ”่ฏฅ่ƒฝๆ‰“ๅผ€

่ฏฆๆƒ…้กตๆต‹่ฏ•
โœ“ ไธป้กตๆœ‰charmanderๅฐ็ซ้พ™ๅ…ฅๅฃ (273ms)
โœ“ ็‚นๅ‡ปๅฐ็ซ้พ™่ฏฆๆƒ…้กตๅบ”่ฏฅๆ˜พ็คบ่ฏฆๆƒ… (1012ms)
โœ“ ่ฟ›ๅ…ฅๅฐ็ซ้พ™่ฏฆๆƒ…้กตๅŽ๏ผŒ่ƒฝๅคŸ่ฟ›ๅ…ฅๅ‰ไธ€ไธช่ฏฆๆƒ…้กต (1514ms)
โœ“ ่ฟ›ๅ…ฅๅฐ็ซ้พ™่ฏฆๆƒ…้กตๅŽ๏ผŒ่ƒฝๅคŸ่ฟ›ๅ…ฅไธ‹ไธ€ไธช่ฏฆๆƒ…้กต (912ms)
โœ“ ่ฟ›ๅ…ฅๅฐ็ซ้พ™่ฏฆๆƒ…้กตๅŽ๏ผŒ่ƒฝๅคŸ่ฟ”ๅ›žไธป้กต (855ms)
# ..
(Screenshots)

- /Users/weimo/mine/code/my-proj/cypress/screenshots/app.cy.js/Pok (1280x720)
edex -- front page can be opened ไธป้กตๅบ”่ฏฅ่ƒฝๆ‰“ๅผ€ (failed).png


====================================================================================================

(Run Finished)


Spec Tests Passing Failing Pending Skipped
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โœ– app.cy.js 01:05 6 5 1 - - โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
โœ– 1 of 1 failed (100%) 01:05 6 5 1 - -


* ็ปˆ็ซฏ่ฟ›็จ‹โ€œ/bin/zsh '-i', '-c', 'npm run test:e2e'โ€ๅทฒ็ปˆๆญข๏ผŒ้€€ๅ‡บไปฃ็ : 1ใ€‚
* ็ปˆ็ซฏๅฐ†่ขซไปปๅŠก้‡็”จ๏ผŒๆŒ‰ไปปๆ„้”ฎๅ…ณ้—ญใ€‚

ไธ€ไธชๆต‹่ฏ•็คบไพ‹ๆ–‡ไปถ

ๆ–‡ๆกฃ https://docs.cypress.io/

ๆŸฅๆ‰พๅ†…ๅฎน cy.contains() cy.get() cy.find() cy.should() ็‚นๅ‡ปๆŒ‰้’ฎ ๆฃ€ๆต‹ๆ•ฐๆฎๅ˜ๅŒ–

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
describe('blog app',function () {

beforeEach(function () {
cy.request('POST','http://localhost:3003/api/testing/reset')
const user = {
name: 'DASB',
username: 'sb',
password: '123456'
}
cy.request('POST','http://localhost:3003/api/users', user)
cy.visit('http://localhost:3000')
})


it('can open site ๅฏไปฅๆ‰“ๅผ€็ฝ‘้กต',function () {
cy.visit('http://localhost:3000')
cy.contains('blogs')
})

it('and its have login form ๆ˜พ็คบ็™ปๅฝ•่กจๅ•',function () {
cy.contains('username')
cy.contains('password')
})

describe('login ็™ปๅฝ•ๆต‹่ฏ•',function () {

it('can not log in with incorrect credentials ้”™่ฏฏไฟกๆฏไธๅฏไปฅ็™ปๅฝ•',function () {
cy.get('#username-login').type('dasb')
cy.get('#password-login').type('dasb')
cy.get('#login-button').click()

cy.contains('error')
})

it('can log in with correct info ๆญฃ็กฎไฟกๆฏๅฏไปฅ็™ปๅฝ•',function () {
cy.get('#username-login').type('sb')
cy.get('#password-login').type('123456')
cy.get('#login-button').click()

cy.contains('success')
})

})

describe('When logged in', function() {
beforeEach(function () {
cy.login({ username: 'sb', password: '123456' })
})

it('A blog can be created', function() {
cy.contains('create a new blog').click()
cy.get('#title').type('ๆ ‡้ข˜')
cy.get('#author').type('ไฝœ่€…')
cy.get('#url').type('้“พๆŽฅ')
cy.get('#likes').type('666')
cy.contains('submit').click()
cy.contains('ๆ ‡้ข˜ ไฝœ่€…')
})

it('open show details and click like!', function () {

cy.createBlog({ title:'Test', author:'T est',url:'hppp:%%test@tset.com',likes:999 })

cy.contains('Test').find('button:first').as('theShowDetailButton') // ๆ‰พๅˆฐshow detailๆŒ‰้’ฎ
// ็„ถๅŽๆˆ‘ไปฌ็‚นๅ‡ปๆŒ‰้’ฎ๏ผŒๆฃ€ๆŸฅไธŠ้ข็š„ๆ–‡ๆœฌๆ˜ฏๅฆๆ”นๅ˜
cy.get('@theShowDetailButton').click()
cy.get('@theShowDetailButton').should('contain', 'hide detail')

cy.get('#add-likes-button').as('theLikeButton') // ๆ‰พๅˆฐshow detailๆŒ‰้’ฎ
cy.contains(999)
cy.get('@theLikeButton').click()
cy.contains(1000)

})


it('blog can be deleted',function () {

cy.createBlog({ title:'this', author:'will',url:'remove',likes:0 })

cy.get('#show-detail-button').as('theShowDetailButton')
cy.get('@theShowDetailButton').click()
cy.contains('this')
cy.contains('will')
cy.contains('remove')

cy.get('#delete-button').click()
cy.contains('delete blog')
cy.should('not.contain','this')
cy.should('not.contain','will')
cy.should('not.contain','remove')

})

describe('the order is correct order by likes',function () {

beforeEach(function () {

cy.createBlog({ title:'first', author:'hehe',url:'haha',likes:0 })
cy.createBlog({ title:'second', author:'hehe',url:'xxxx',likes:666 })
cy.createBlog({ title:'third', author:'hehe',url:'xxxx',likes:888 })
cy.createBlog({ title:'fourth', author:'hehe',url:'xxxx',likes:888 })
cy.createBlog({ title:'fifth', author:'hehe',url:'xxxx',likes:111 })
cy.createBlog({ title:'sixth', author:'will',url:'remove',likes:1001 })
})

it('have six blogs in correct order',function () {

// open all details
cy.get('#blog-list div').find('button:first').click({ multiple: true })

// blogList should in correct order
cy.get('#blog-list div')
.first().should('contain','sixth').should('contain',1001)
.next().should('contain','third').should('contain',888)
.next().should('contain','fourth').should('contain',888)
.next().should('contain','second').should('contain',666)
.next().should('contain','fifth').should('contain',111)
.next().should('contain','first').should('contain',0)

})

it('click fourth blog like! button, then the order change',function () {
// open all details
cy.get('#blog-list div').find('button:first').click({ multiple: true })

cy.get('#blog-list div')
.first()
.next()
.next().should('contain','fourth').should('contain',888).find('#add-likes-button').click()

cy.get('#blog-list div')
.first()
.next()
.next().should('contain','fourth').should('contain',889)
})

// describe('click like on fourth blog and check order',function () {

// beforeEach('click like',function () {

// cy.get('#blog-list div').find('button:first').click({ multiple: true })

// cy.get('#blog-list div')
// .first()
// .next()
// .next().should('contain','fourth').should('contain',888).find('#add-likes-button').click()
// })

// it('the order in correct',function () {
// cy.get('#blog-list div')
// .first()
// .next().should('contain','fourth').should('contain',889)

// })
// })

})
})
})