hello

Hello ~ 欢迎来到我的博客 👏👏

npx在线调用

npx 指令从npm5.2就内置了,可以在线调用npm包而不用下载到本地再执行

ie: cowsay 指令

1
2
3
4
5
6
7
8
9
10
➜  ~ npx cowsay owow
______
< owow >
------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||
➜ ~

可以直接编译&运行ts文件

1
npx ts-node script.ts

可以执行 create-react-app 脚本

1
npx create-react-app .

也可以调用自己写的GitHub gist代码片段

1
2
3
4
5
➜  my-project git:(main) npx https://gist.github.com/miloweimo/491623a136c0ceed0371c10ae95c86a0
Need to install the following packages:
gist:491623a136c0ceed0371c10ae95c86a0
Ok to proceed? (y)
helllllloooooo~~

参考

我写的hellojs https://gist.github.com/miloweimo/491623a136c0ceed0371c10ae95c86a0

别人写的hellojs https://gist.github.com/Tynael/0861d31ea17796c9a5b4a0162eb3c1e8

别人写的readme https://gist.github.com/CarsonSlovoka/ad04ee083bd2dde825060160546b8059

免输密码直接scp传文件的研究

❌ yes指令

1
(yes ${PWD} | head -2 && yes y | head -1) | scp test xx@yy:zz

实测不行

❌ scp -S参数

没有这个写法

✅添加公钥到服务器

可以直接scp

✅sshpass输密码

可以但是部分系统不支持

前端项目添加 打包脚本 部署脚本 生成改动日志脚本

打包脚本

package.json 添加

1
2
3
// ..
"zip": "zip -r dist-$(git rev-parse --abbrev-ref HEAD)-$npm_package_name-$npm_package_version-$(date +'%Y_%m_%d').zip dist/ && open .",
"bz": "npm run build && npm run zip"

然后运行 npm run bz 就能直接打开打包好的zip包目录

阅读更多

cypress测试框架基础用法

E2E库 Cypress

端到端End to End (E2E)测试,将系统作为一个整体的测试方法

准备工作

首先有一个前端项目的打包文件,可以启动的后端服务,来做自动化e2e测试

安装配置

1
npm install --save-dev cypress # 将 Cypress 安装到前端 ,作为开发依赖项

配置package.json 添加脚本 cypress:opentest: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)

// })
// })

})
})
})

:src动态导入图片路径

对于webpack

1
const img = require(`./img/${name}.png`)
1
<img :src="img" />

或者直接在html中使用

1
2
<img :src="require(`./img/${name}.png`)" />
<img :src="require(`@/img/${name}.png`)" />

对于vite

1
2
// ..
activeIcon: new URL('@/assets/icon/intelligent-contro.png', import.meta.url).href,
1
2
3
4
5
<img
:src="item.activeIcon"
height="40"
alt=""
>

3-2-1 备份方案

The 3-2-1 backup strategy simply states that you should have 3 copies of your data (your production data and 2 backup copies) on two different media (disk and tape) with one copy off-site for disaster recovery

3-2-1的备份策略只是指出,您应该在两个不同的媒体(磁盘和磁带)上拥有3份数据副本(您的生产数据和2份备份副本),其中一份副本副本以供灾难恢复