MockingBird

记录 Web 前端成长旅程


  • 首页

  • 分类

  • 归档

  • 标签

Puppeteer 多 iframe 场景表单代填

发表于 2021-05-30   |   分类于 Puppeteer   |  

一、初始化

创建一个用来测试的目录,然后安装 puppeteer 依赖项

1
2
3
4
mkdir puppeteer-auto-fill
cd puppeteer-auto-fill
npm init
npm i puppeteer

创建一个 index.js 文件用来编写测试脚本。往package.json中添加scripts:

1
2
3
4
"scripts": {
"start": "node index.js",
"debug": "env DEBUG=\"puppeteer:*\" node --inspect-brk index.js"
},

Chrome DevTool 调试方式:

  1. 打开 Chrome,输入 chrome://inspect/#devices
  2. 运行 debug 脚本后,可以看到调试页面,点击 inpect 进入断点调试image.png

二、代填逻辑

1. 模拟多 iframe 嵌套场景

iframe嵌套关系如下:

2. 代填脚本

  • 启动参数说明:

headless: false 是否在后台进行
devtools: true 打开页面后是否显示开发者工具
slowMo: 0 操作延时,把这个数字可以调大相当于慢放

  • Frame 查找:
    通过 Frame.$(.selector) 的方式查找返回选择器条件的 iframe,如果找不到会返回 null,否则会返回一个 ElementHandle 对象,然后在通过 .contentFrame() 将其转换为 Frame 对象。
  • Page 和 Frame 都有 .waitForSelector() 方式用于等待元素加载完成。
  • 通过 .waitForTimeout() 方法来实现延时输入效果。

​

完整代码如下:

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
const puppeteer = require('puppeteer');

(async () => {
const browser = await puppeteer.launch({ headless: false, devtools: true, slowMo: 0 });
const page = await browser.newPage();
await page.goto('http://localhost/frameset/index.html');
tryFillForm(page, [
{ type: 'iframe', selector: '#app > iframe:nth-child(3)' },
{ type: 'iframe', selector: 'body > iframe' },
{ type: 'text', selector: '#login', value: 'admin' },
{ type: 'password', selector: '#pwd', value: '12345678' },
]);
})();


let tryFillForm = async function(page, autoFillInfo) {
try {
await page.evaluateOnNewDocument(() => {
Object.defineProperty(navigator, 'webdriver', {
get: () => false,
});
});
let waitEntry = page;
let frameDeep = 0;
for(let i = 0; i < autoFillInfo.length; i++) {
let info = autoFillInfo[i];
if('iframe' === info.type) {
await waitEntry.waitForSelector(info.selector, {timeout : 5000});
let iframe = null;
if(frameDeep === 0) {
iframe = await waitEntry.mainFrame().$(info.selector);
} else {
iframe = await waitEntry.$(info.selector);
}
//frames.forEach(f=>console.log(f.name()));
if (iframe) {
//console.log(iframe.url());
waitEntry = await iframe.contentFrame();
}
// break;
frameDeep++;
}
}
for(let i = 0; i < autoFillInfo.length; i++) {
let info = autoFillInfo[i];
if('iframe' !== info.type) {
await waitEntry.waitForSelector(info.selector, {timeout : 120000});
}
}
for(let i = 0; i < autoFillInfo.length; i++) {
let info = autoFillInfo[i];
let type = info.type;
if('iframe' !== type) {
if(!type) {
type = await waitEntry.evaluate((selector) => {
let element = document.querySelector(selector);
if(element) {
let nodeName = element.nodeName.toLowerCase();
let type = nodeName;
if(nodeName === 'input') {
type = element.type || 'text';
}
if(type === 'submit') {
type = 'button';
}
return type;
}
return null;
}, info.selector);
}
if(type) {
console.log(info);
if(type === 'text' || type === 'password') {//填充节点
//有些节点必须通过type来改变值,有些节点只能直接改变value,所以两个都做,先type然后再改值
await waitEntry.type(info.selector, info.value);
await waitEntry.evaluate((data) => {
return document.querySelector(data.selector).value = data.value
}, info);
// 等待 0.5s
await waitEntry.waitForTimeout(500);
} else if(type == 'select') {//combbox
await waitEntry.select(info.selector, info.value);
} else if(type == 'button') {
waitEntry.click(info.selector, {delay : 1000});
} else {
waitEntry.click(info.selector);
}
}
}
}
} catch(e) {
console.error(e);
}
};

Narrowing 类型收缩 - 《TypeScript学习笔记》

发表于 2021-05-24   |   分类于 typescript   |  

type guards (类型守卫) 和 narrowing

通过 if 条件语句对类型进行判断,TypeScript会对变量的类型进行分析,以明白这个分支下变量的具体类型,这个过程叫做 type guards 。而这整个由不确定类型到确定具体类型到过程就叫做 narrowing ,下面有几种方式可以对类型进行校验。

typeof 守卫

可以通过 typeofe 运算符对类型进行最基础对判断:

  • string
  • number
  • bigint
  • boolean
  • symbol
  • undefined
  • object
  • function

Truthiness narrowing(是否为真)

在 if 表达式内,JavaScript 对条件是否为真对判断并不是决定对,它会把非 boolean 类型对表达式转化为 boolean 类型进行判断。下面的值会被转化为 false :

  • 0
  • NaN
  • ""(空字符串)
  • 0n (大整型0)
  • null
  • undefined

除了上面这些值会被转化为false,其它都会被转发为true。

Equality narrowing(是否相等)

我们可以通过 ===,!==,==,!= 对类型进行收缩。比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function example(x: string | number, y: string | boolean) {
if (x === y) {
// We can now call any 'string' method on 'x' or 'y'.
x.toUpperCase();
// ^?
y.toLowerCase();
// ^?
} else {
console.log(x);
// ^?
console.log(y);
// ^?
}
}

instanceof narrowing(原型校验)

这种方式用来检查通过new对方式创建出来对对象对类型。

1
2
3
4
5
6
7
function logValue(x: Date | string) {
if (x instanceof Date) {
console.log(x.toUTCString());
} else {
console.log(x.toUpperCase());
}
}

赋值操作

TypeScript会根据赋值操作符=右边对类型决定变量的类型

1
let x = Math.random() < 0.5 ? 10 : "hello world!";

这里的 x 会被推断为 string | number 两种类型。

类型断言

对于自定义类型,可以通过 parameterName is Type 的方式断言某个变量是 Type 类型,其中 parameterName 必须为函数的参数

1
2
3
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}

联合类型

通过 | 符号来表示字面类型的组合,如:

1
2
3
4
5
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}

never 类型

never 类型表示一个不应该存在的状态,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
type Shape = Circle | Square;

function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
case "square":
return shape.sideLength ** 2;
default:
const _exhaustiveCheck: never = shape;
return _exhaustiveCheck;
}
}

Nginx 配置虚拟主机

发表于 2018-10-19   |  

本文记录了在 Mac OSX 下,通过 Nginx 1.15.5 来配置虚拟主机的过程。

0. 准备工作

本次模拟的是效果是,通过浏览器访问 www.shop.com 和 api.shop.com 能定位到不同的网站目录下:

  • www.shop.com -> ~/Sites/home
  • api.show.com -> ~/Sites/api

上面的两个域名是随便取的,为了能让它指向本地,我们需要修改 vhost 文件:

1
sudo vim /private/etc/hosts

追加:

1
2
127.0.0.1       www.shop.com
127.0.0.1 api.shop.com

1. 通过 brew 安装 nginx

通过 homebrew 安装 nginx:

1
brew install nginx

安装完成后通过 brew services start nginx 启动 nginx 服务

2. 修改 nginx 配置文件

接下来需要对 nginx 的配置文件(/usr/local/etc/nginx/nginx.conf)进行修改,打开 nginx.conf 确保下面这行配置已经启用:

1
include servers/*;

3. 添加虚拟主机配置文件

然后,在 /usr/local/etc/nginx/services 目录下,创建两个配置文件(home.conf, api.conf)用于对上面两个 host 进行配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# home.conf
server {
listen 80;
server_name www.shop.com;

location / {
root /Users/mcbird/Sites/main;
index index.html index.htm;
}
}

# api.conf
server {
listen 80;
server_name api.shop.com;

location / {
root /Users/mcbird/Sites/api;
index index.html index.htm;
}
}

4. 加载新的 nginx 配置

修改完 nginx 配置文件,并不会立即生效,接下来可以先对配置文件进行检查:

1
2
3
4
# 检查配置文件是否有语法错误
sudo nginx -t
nginx: the configuration file /usr/local/etc/nginx/nginx.conf syntax is ok
nginx: configuration file /usr/local/etc/nginx/nginx.conf test is successful

检查成功后,通过 reload 信号重新加载配置文件:

1
sudo nginx -s reload

5. 完成

通过浏览器访问 www.shop.com 就会打开 /Users/mcbird/Sites/main/index.html 文件;api.shop.com 会打开 /Users/mcbird/Sites/api/index.html

Webpack 是如何加载模块的

发表于 2018-05-23   |   分类于 javascript   |  

Webpack 在前端开发中作为模块打包工具非常受开发者的青睐,丰富的 loader 使它可以实现各种各样的功能。本文将通过 webpack 来打包一个 js 文件,看看 webpack 是如何加载各个模块的。

两个简单的源文件

为了方便分析 webpack 加载模块的原理,我们准备了两个文件:

hello.js

1
2
3
4
5
6
7
const hello = {
say: arg => {
console.info('hello ' + arg || 'world');
}
};

export default hello;

index.js

1
2
3
import Hello from './hello';

Hello.say('man');

index.js 作为入口文件,引用了 hello.js 模块。

Webpack 打包

在命令行执行 webpack index.js bundle.js 对入口文件进行打包,生成 bundle.js ,大体结构为(为了方便阅读,我删除了部分多余的代码):
2018-05-23-17-33-27-2018523173328

可以看到,最终生成的文件以 (function (modules) {})([模块1, 模块2]) 的方式启动,我们定义的模块被包装成一个个匿名函数,然后以数组的形式传递个一个匿名函数 function (modules) {},在这个匿名函数中定义了一个 __webpack_require__() 函数,用来加载模块,最后,通过 return __webpack_require__(__webpack_require__.s = 0); 来加载第一个模块 index.js

webpack_require() 函数

该函数接收一个 moduleId 作为参数,这个参数就是各个模块在数组中的索引,

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
function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/
if (installedModules[moduleId]) {
/******/
return installedModules[moduleId].exports;
/******/
}
/******/ // Create a new module (and put it into the cache)
/******/
var module = installedModules[moduleId] = {
/******/
i: moduleId,
/******/
l: false,
/******/
exports: {}
/******/
};
/******/
/******/ // Execute the module function
/******/
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/
module.l = true;
/******/
/******/ // Return the exports of the module
/******/
return module.exports;
/******/
}

其中 installedModules 是用来缓存执行过的模块。通过 modules[moduleId].call() 来执行模块,最后返回模块的 exports。

模块接受的参数

以 hello.js 模块为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function (module, __webpack_exports__, __webpack_require__) {

"use strict";
const hello = {
say: arg => {
console.info('hello ' + arg || 'world');
}
};

/* harmony default export */
__webpack_exports__["a"] = (hello);

/***/
})

webpack 会向模块传递 module, __webpack_exports__, __webpack_require__ 三个参数,前两个是用来导出模块内的变量,第三个参数为前面介绍的 __webpack_require__() 的引用,用来导入其它模块。

函数节流(throttle)与函数去抖(debounce)的区别

发表于 2016-09-08   |   分类于 javascript   |  

以前在做一些性能小优化的时候会用到 “debounce” 这种技巧,特别是在处理一些 DOM 事件上。今天了解到原来还有一个跟它类似的叫 “throttle” 的玩意,一开始看上去感觉没多大的差别,仔细阅读代码,发现它们无论是在代码逻辑和应用场景上都是有很大的区别的。

函数节流(throttle)是什么

函数节流(throttle)用来限制单位时间的函数被调用的最大次数

比方说下面这段代码中的 fn() 每 100ms 只能执行一次,那么 1s 内它最多只能执行10次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var throttle = function(action, delay){
var last = 0;
return function(){
var curr = +new Date();
if (curr - last > delay)
action.apply(this, arguments);
last = curr;
};
};

func () {
// ...
}

var fn = throttle(func, 100);

函数去抖(debounce)是什么

函数去抖(debounce)规定了函数在指定的时间内不能被再次调用,如果在这期间被调用了,那么执行等待时间将会被刷新

比方说下面这段代码中的 fn() 被调用后,要等待 100ms 函数 func 才会被执行,如果在这期间, fn() 又被调用了,那么等待时间将从调用的那一刻重新算起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var debounce = function(action, idle){
var last;
return function(){
var ctx = this, args = arguments;
clearTimeout(last);
last = setTimeout(function(){
action.apply(ctx, args);
}, idle);
};
};

func () {
// ...
}

var fn = debounce(func, 100);

Demo

借用一个第三方示例:


参考资料:

  • https://css-tricks.com/the-difference-between-throttling-and-debouncing/
12…6
McBird

McBird

分享自己的学习经历,实用技巧

27 日志
13 分类
25 标签
GitHub 微博 知乎
© 2015 - 2021 McBird
由 Hexo 强力驱动
主题 - NexT.Muse