Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

auto-chrome

Package Overview
Dependencies
Maintainers
1
Versions
83
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

auto-chrome - npm Package Compare versions

Comparing version 1.0.7 to 1.1.0

lib/Connection.js

10

History.md

@@ -39,2 +39,10 @@ ## 版本更新

* Chrome.close()改用关闭所有标签的方式优雅的退出浏览器。在实测中发现使用DevTools协议的"Browser.close"消息和childProcess.kill()均无法关闭浏览器。
* Chrome.close()改用关闭所有标签的方式优雅的退出浏览器。在实测中发现使用DevTools协议的"Browser.close"消息和childProcess.kill()均无法关闭浏览器。
### 1.1.0
* 使用ipc pipe通信替换ws WebSocket
* 简化自动导航逻辑
* waitLoad()更名为autoNav()

12

lib/Chrome.js

@@ -9,3 +9,3 @@ "use strict";

/**
* @param {Object} args.ws WebSocket实例
* @param {Object} args.connection 进程连接实例
* @param {Boolean} args.process 子进程chrome实例

@@ -17,5 +17,5 @@ * @param {Boolean} args.options.ignoreHTTPSErrors 是否忽略https错误

const { ws, options, process } = args;
const { connection, options, process } = args;
super(ws);
super(connection);

@@ -45,6 +45,7 @@ this.options = options;

// 启用目标监听
await this.send('Target.setDiscoverTargets', { discover: true });
// 如果浏览器默认标签未完成初始化,则等待标签初始标签准备就绪后再执行
await this.autoLoad(2000);
await this.autoNav();

@@ -67,2 +68,3 @@ }

const promise = this.closePageById(targetId);
allPromise.push(promise);

@@ -84,3 +86,3 @@ this.pages.delete(targetId);

this.send('Target.createTarget', { url }),
this.autoLoad(2000)
this.autoNav()
]);

@@ -87,0 +89,0 @@

"use strict";
const logger = require('loggercc');
const EventEmitter = require('events');
const timerPromise = require('zpromise/timer.js');
const Page = require('./Page.js');
const helper = require('./helper.js');
const { timechain, restartPromise, sleep } = helper;
const { timechain, sleep } = helper;
class ChromeEvent {
constructor(ws) {
class ChromeEvent extends EventEmitter {
constructor(connection) {
this.ws = ws;
super();
this.connection = connection;
this.id = 1; // 消息自增id

@@ -18,6 +21,2 @@ this.callbacks = new Map(); // WebSocket异步消息队列

this.newPagePromise = new restartPromise(3000, error => {
logger.warn('newPagePromise等待超时');
})
}

@@ -35,5 +34,6 @@ /**

this.ws.send(message); // 发送消息
this.connection.send(message); // 发送消息
return new Promise((resolve, reject) => {
this.callbacks.set(id, {

@@ -44,6 +44,11 @@ resolve,

});
timechain.set(reject, `${method}消息超时`);
}).catch(error => {
logger.error(error);
return { error };
});

@@ -69,4 +74,6 @@

if (callback) {
timechain.delete(callback.reject)
timechain.delete(callback.reject);
this.callbacks.delete(id);
if (error) {

@@ -77,2 +84,3 @@ callback.reject(error);

}
}

@@ -89,11 +97,16 @@

if (method === 'Target.receivedMessageFromTarget') {
const { targetId, message } = params;
const page = this.pages.get(targetId);
if (page) {
page.message(message);
}
} else {
if (this[method]) {
this[method](params);
}
}

@@ -105,77 +118,21 @@

/**
* 等待页面装载
*/
async loadPage() {
if (this.newPagePromise.state !== 'pending') {
this.newPagePromise = this.newPagePromise.restart();
}
await Promise.all([
this.newPagePromise,
this.page.waitCtx(),
this.page.waitLoad()
])
}
/**
* 自动导航(主动监测)
* @param {Number} delay 等待导航最大毫秒数
*/
async autoLoad(delay = 1200) {
async autoNav(time = 3000) {
const { page } = this;
const { contextCreateTime } = this;
if (page.sessionId) {
// 每间隔200ms检查一次context是否更新
for (let i = 1; i <= (time / 200); i++) {
const { contextId, ctxPromise, loadPromise, ctxTime } = page;
await sleep(200);
if (contextId) {
if (contextCreateTime !== this.contextCreateTime) break;
await Promise.all([ctxPromise, loadPromise]);
}
// 循环监测
for (let i = 1; i <= (delay / 100); i++) {
await this.page.loadPromise;
await sleep(100);
await sleep(1000);
// 原标签
if (page === this.page) {
if (ctxTime !== page.ctxTime) {
// 为url重定向保留时间
await sleep(800)
await page.loadPromise
return
}
}
// 新标签
else {
await this.autoLoad()
return
}
}
} else {
await Promise.all([page.waitCtx(), page.waitLoad()]);
}
} else {
await this.loadPage();
}
}

@@ -208,3 +165,3 @@ /**

logger.success(`创建新标签`)
logger.success(`创建新标签`);

@@ -219,2 +176,4 @@ const { targetId } = targetInfo;

this.emit('CreatedPage', page);
// 通过给定targetId获取sessionId

@@ -225,4 +184,2 @@ const { sessionId } = await this.send('Target.attachToTarget', { targetId })

this.keyboard = page.keyboard;
this.mouse = page.mouse;

@@ -232,2 +189,4 @@

this.keyboard = page.keyboard;
const list = [

@@ -248,5 +207,3 @@ page.send('Page.enable'),

if (this.disableDownload === true) {
list.push(page.send('Page.setDownloadBehavior', {
"behavior": "deny"
}));
list.push(page.send('Page.setDownloadBehavior', { "behavior": "deny" }));
}

@@ -256,4 +213,2 @@

this.newPagePromise.resolve();
}

@@ -264,10 +219,8 @@

const { type, url, title } = targetInfo;
// target为page类型
if (type === 'page') {
if (targetInfo.type === 'page') {
this.page.url = url;
this.page.url = targetInfo.url;
this.page.title = title;
this.page.title = targetInfo.title;

@@ -287,4 +240,27 @@ }

}
/**
* 订阅事件并等待触发
* @param {String} name 事件名称
* @param {Function} callback 回调
* @param {Number} timer 等待超时时间
*/
async oncePromise(name, callback, timer = 3000) {
const promise = new timerPromise(timer);
this.once(name, async function (value) {
if (callback) {
await callback(value);
}
promise.resolve(value);
});
return promise;
}
}
module.exports = ChromeEvent;

@@ -38,3 +38,3 @@ "use strict"

return new Element(this.page, remote)
return new Element(this.page, remote);

@@ -52,6 +52,6 @@ }

functionDeclaration: (element, selector) => {
return element.querySelector(selector)
return element.querySelector(selector);
},
arguments: [{ objectId }, { value: selector }]
})
});

@@ -94,3 +94,3 @@ const { className, description } = remote;

if (className === 'TypeError') {
logger.warn(new Error(description))
logger.warn(new Error(description));
return

@@ -110,3 +110,5 @@ }

for (const item of result) {
const { enumerable, value } = item;
if (enumerable === true) {

@@ -116,2 +118,3 @@ const element = this.create(value);

}
}

@@ -231,15 +234,15 @@

const result = await this.getBoundingRect()
const result = await this.getBoundingRect();
let { x, y, height, innerHeight } = result;
const { x, y, height, innerHeight } = result;
let centreY = (innerHeight - height) / 2;
const centreY = (innerHeight - height) / 2;
y = y - centreY;
const scrollY = y - centreY;
await clicker.scroll(x, y)
await clicker.scroll(x, scrollY);
} else {
await this.loopScroll()
await this.loopScroll();

@@ -252,3 +255,3 @@ }

* 在每个单次滚动后重新计数坐标,防止DOM更新后原坐标失效
* 当chrome不稳定导致事件未执行时,采用实时坐标提供了错误补救的机会
* 当chrome不稳定导致事件未执行时,采用实时坐标提供了纠正错误的机会
*/

@@ -266,9 +269,9 @@ async loopScroll() {

// 模拟随机坐标,让每次的滑动轨迹都不一样
let startX = Math.round(innerWidth * (0.3 + Math.random() * 0.4))
let startY = Math.round(innerHeight * (0.8 + Math.random() * 0.15))
const startX = Math.round(innerWidth * (0.3 + Math.random() * 0.4));
const startY = Math.round(innerHeight * (0.8 + Math.random() * 0.15));
let endX = Math.round(startX + Math.random() * 0.1)
let endY = Math.round(innerHeight * (0.1 + Math.random() * 0.15))
const endX = Math.round(startX + Math.random() * 0.1);
let endY = Math.round(innerHeight * (0.1 + Math.random() * 0.15));
let moveY = startY - endY
const moveY = startY - endY;

@@ -293,3 +296,3 @@ // 末端补齐

await this.page.touch.slide({ start, end })
await this.page.touch.slide({ start, end });

@@ -299,3 +302,3 @@ // 分多次发送滑动事件

await this.loopScroll()
await this.loopScroll();

@@ -312,3 +315,3 @@ }

const result = await this.getBoundingRect()
const result = await this.getBoundingRect();

@@ -321,6 +324,3 @@ let { x, y, width, height } = result;

await Promise.all([
this.page.clicker.click(x, y),
this.page.chrome.autoLoad(2000)
])
await this.page.clicker.click(x, y);

@@ -331,18 +331,23 @@ return result;

/**
* 低仿真点击元素,仅适用于PC端
* 高仿真点击元素
*/
async lowClick() {
async clickNav() {
const { objectId } = this;
// await this.label()
const click = this.frame.callFunctionOn({
functionDeclaration: element => { element.click(); },
arguments: [{ objectId }]
})
const result = await this.getBoundingRect();
let { x, y, width, height } = result;
// 定位到元素中心
x = x + width / 2;
y = y + height / 2;
await Promise.all([
click,
this.page.chrome.autoLoad(2000)
]);
this.page.chrome.autoNav(3000),
this.page.clicker.click(x, y)
])
return result;
}

@@ -349,0 +354,0 @@ /**

@@ -24,6 +24,6 @@ "use strict";

if (objectId === undefined) {
throw new Error(`缺少objectId参数!`)
throw new Error(`缺少objectId参数!`);
}
const { result } = await this.page.send('Runtime.callFunctionOn', {
const { result, error } = await this.page.send('Runtime.callFunctionOn', {
objectId,

@@ -33,4 +33,9 @@ awaitPromise: true,

...options
})
});
if (error) {
throw logger.error(new Error(error));
// throw new Error(error.message);
}
const { className, subtype, description } = result;

@@ -54,3 +59,3 @@

let { result } = await this.page.send('Runtime.evaluate', {
const { result } = await this.page.send('Runtime.evaluate', {
returnByValue: false,

@@ -62,13 +67,13 @@ awaitPromise: true,

let { className, subtype, description } = result
const { className, subtype, description } = result;
if (className === 'TypeError' || subtype === 'error') {
logger.error(new Error(description))
logger.error(new Error(description));
}
return result
return result;
} catch (error) {
logger.error(error)
logger.error(error);

@@ -80,2 +85,2 @@ }

module.exports = Frame
module.exports = Frame;
"use strict";
const timeChain = require('timechain');
const timerPromise = require('zpromise/timer.js');
const restartPromise = require('zpromise/restart.js');

@@ -11,12 +9,5 @@ const timechain = new timeChain({ delay: 10000 });

timechain,
timerPromise,
restartPromise,
assert(value, message) {
if (!value) {
throw new Error(message);
}
},
sleep(time = 0) {
return new Promise(resolve => setTimeout(resolve, time))
return new Promise(resolve => setTimeout(resolve, time));
},
}

@@ -5,16 +5,10 @@ "use strict";

const childProcess = require('child_process');
const readline = require('readline');
const WebSocket = require('ws');
const Connection = require('./Connection.js');
const Chrome = require('./Chrome.js');
const helper = require('./helper.js');
const { timerPromise } = helper;
async function main(options) {
const { args = [], userDataDir, profileDir, port = 9222, } = options;
const { args = [], userDataDir, profileDir, } = options;
const { headless, devtools, executablePath, } = options;
args.push(`--remote-debugging-port=${port}`);
if (userDataDir) {

@@ -41,4 +35,9 @@ args.push(`--user-data-dir=${userDataDir}`);

args.push('--remote-debugging-pipe');
// 启动浏览器
const chromeProcess = childProcess.execFile(executablePath, args);
const chromeProcess = childProcess.spawn(executablePath, args, {
env: process.env,
stdio: ['ignore', 'ignore', 'ignore', 'pipe', 'pipe']
});

@@ -49,55 +48,8 @@ chromeProcess.on('error', error => {

chromeProcess.on('message', message => {
logger.info(message);
});
const [, , , pipeWrite, pipeRead] = chromeProcess.stdio;
const rl = readline.createInterface({
input: chromeProcess.stderr
});
const connection = new Connection(pipeWrite, pipeRead);
const linePromise = new timerPromise(30000);
rl.on('line', function (data) {
if (data.indexOf('ws://') >= 0) {
const url = data.replace('DevTools listening on ', '');
linePromise.resolve(url);
}
});
// 获取webSocket连接地址
const webSocketUrl = await linePromise.catch(error => {
throw error
})
let ws;
try {
ws = new WebSocket(webSocketUrl, { perMessageDeflate: false });
} catch (error) {
chromeProcess.kill();
throw error;
}
const awaitOpen = new timerPromise(30000);
ws.on('open', awaitOpen.resolve);
ws.on('error', awaitOpen.reject);
await awaitOpen.then(function () {
logger.success('Chrome连接成功');
}).catch(function (error) {
logger.error(new Error('Chrome连接失败'));
throw error;
})
const chrome = new Chrome({
ws,
connection,
options,

@@ -107,3 +59,3 @@ process: chromeProcess,

ws.on('message', chrome.message.bind(chrome));
connection.bind(chrome);

@@ -110,0 +62,0 @@ await chrome.init();

"use strict"
const keyDefinitions = require('./USKeyboard.js');
const { assert, sleep } = require('./helper.js');
const { sleep } = require('./helper.js');

@@ -16,3 +16,3 @@ class Keyboard {

this.pressedKeys = new Set();
}

@@ -33,3 +33,3 @@

const text = options.text === undefined ? description.text : options.text;
await this.send('Input.dispatchKeyEvent', {

@@ -47,3 +47,3 @@ type: text ? 'keyDown' : 'rawKeyDown',

});
}

@@ -82,4 +82,7 @@

const definition = keyDefinitions[keyString];
assert(definition, `Unknown key: "${keyString}"`);
if (definition === undefined) {
throw new Error(`Unknown key: "${keyString}"`);
}
if (definition.key)

@@ -98,12 +101,18 @@ description.key = definition.key;

if (definition.location)
if (definition.location) {
description.location = definition.location;
}
if (description.key.length === 1)
if (description.key.length === 1) {
description.text = description.key;
}
if (definition.text)
if (definition.text) {
description.text = definition.text;
if (shift && definition.shiftText)
}
if (shift && definition.shiftText) {
description.text = definition.shiftText;
}

@@ -121,2 +130,3 @@ // if any modifiers besides shift are pressed, no text should be sent

async up(key) {
const description = this.keyDescriptionForString(key);

@@ -134,2 +144,3 @@

});
}

@@ -147,5 +158,6 @@

key: char,
unmodifiedText: char
unmodifiedText: char,
isKeypad: true
});
}

@@ -155,17 +167,19 @@

* @param {string} text
* @param {{delay: (number|undefined)}=} options
*/
async type(text) {
let chars = []
let chars = [];
for (const char of text) {
// 英文输入
if (keyDefinitions[char]) {
if (chars.length) {
await this.sendCharacter(chars.join(''));
chars = []
chars = [];
}
await this.press(char, { delay: 20 }); // 键盘输入
} else {
chars.push(char)
}
await sleep(100)
// 非英文输入
else {
chars.push(char);
}
await sleep(100);
}

@@ -184,4 +198,5 @@ // 直接赋值

await this.down(key, options);
if (options && options.delay)
if (options && options.delay) {
await new Promise(f => setTimeout(f, options.delay));
}
await this.up(key);

@@ -188,0 +203,0 @@ }

"use strict";
const logger = require('loggercc');
const { Mouse, Touch } = require('./Input.js');
const Mouse = require('./Mouse.js');
const Touch = require('./Touch.js');
const Keyboard = require('./Keyboard.js');

@@ -17,3 +18,3 @@ const Frame = require('./Frame.js');

this.chrome = chrome;
this.ws = chrome.ws;
this.connection = chrome.connection;
this.targetId = targetId;

@@ -70,4 +71,4 @@

const result = await Promise.all([
this.send('Page.navigate', { url }),
this.chrome.autoLoad(2000)
this.chrome.autoNav(),
this.send('Page.navigate', { url })
]).catch(() => {

@@ -108,8 +109,10 @@ logger.warn(`打开链接${url}超时`);

const { absolute, relative } = options
const { absolute, relative } = options;
let entry;
if (absolute) {
var entry = entries[absolute]
entry = entries[absolute];
} else if (relative) {
var entry = entries[currentIndex + relative]
entry = entries[currentIndex + relative];
}

@@ -127,3 +130,3 @@

* @param {Function} func
* @param {*} args
* @param {Array} args
*/

@@ -158,3 +161,3 @@ async run(functionDeclaration, ...args) {

return await this.document.$$(selector)
return await this.document.$$(selector);

@@ -168,3 +171,3 @@ }

return this.document.create(remote)
return this.document.create(remote);

@@ -186,3 +189,3 @@ }

/**
* 高仿真点击元素
* 点击元素
* @param {String} selector CSS选择器

@@ -204,6 +207,6 @@ */

/**
* 低仿真点击元素,仅适用于PC端
* 点击元素,内置导航
* @param {String} selector CSS选择器
*/
async lowClick(selector) {
async clickNav(selector) {

@@ -214,3 +217,3 @@ const element = await this.$(selector);

await element.lowClick();
await element.clickNav();

@@ -229,4 +232,4 @@ return element;

await Promise.all([
this.keyboard.press("Enter"),
this.chrome.autoLoad(2000)
this.chrome.autoNav(3000),
this.keyboard.press("Enter")
])

@@ -299,3 +302,3 @@

await this.chrome.closePageById(this.targetId)
await this.chrome.closePageById(this.targetId);

@@ -302,0 +305,0 @@ }

"use strict"
const logger = require('loggercc');
const EventEmitter = require('events');
const timerPromise = require('zpromise/timer.js');
const restartPromise = require('zpromise/restart.js');
const helper = require('./helper.js');
const { timechain, restartPromise } = helper;
const { timechain } = helper;

@@ -11,5 +14,6 @@ /**

*/
class PageEvent {
class PageEvent extends EventEmitter {
constructor() {
constructor(chrome) {
super();

@@ -21,32 +25,6 @@ this.id = 1 // 消息自增id

this.ctxPromise = new restartPromise(5000, error => {
logger.warn('ctxPromise等待超时');
})
this.loadPromise = new restartPromise(10000);
this.loadPromise = new restartPromise(chrome.options.loadTimeout || 10000, error => {
logger.warn('loadPromise等待超时');
})
}
/**
* 重置ctx
*/
waitCtx() {
this.ctxPromise = this.ctxPromise.restart();
return this.ctxPromise;
}
/**
* 重置lode,等待页面完成加载
*/
waitLoad() {
this.loadPromise = this.loadPromise.restart();
return this.loadPromise;
}
/**
* 发送带session的嵌套消息

@@ -67,24 +45,28 @@ * @param {String} method 消息名称

const message = {
id: 0, // id值可指定任意数值,不会建立响应关联,因此没有实际用途,但又不可缺省
method: "Target.sendMessageToTarget",
params: {
sessionId: this.sessionId,
message: JSON.stringify(subMessage)
'id': 0, // id值可指定任意数值,不会建立响应关联,因此没有实际用途,但又不可缺省
'method': "Target.sendMessageToTarget",
'params': {
'sessionId': this.sessionId,
'message': JSON.stringify(subMessage)
}
}
// 发送消息
this.ws.send(JSON.stringify(message));
this.connection.send(JSON.stringify(message)); // 发送消息
return new Promise((resolve, reject) => {
this.callbacks.set(id, { resolve, reject, method });
timechain.set(reject, `“${method}”消息等待超时`);
}).catch(error => {
if (error instanceof Object) {
logger.error(String(Object.values(error)));
return { error }
const errorInfo = String(Object.values(error));
logger.error(errorInfo);
return { error: errorInfo };
} else {
logger.error(String(error));
return { error }
return { error };
}
})

@@ -106,4 +88,4 @@

this.callbacks.delete(message.id);
timechain.delete(callback.reject);
this.callbacks.delete(message.id);

@@ -135,14 +117,18 @@ if (message.error) {

let { objectId } = await this.frame.evaluate({ expression: "document" });
const { origin, id } = context;
const { objectId } = await this.frame.evaluate({ expression: "document" });
this.document.objectId = objectId;
this.contextId = context.id;
this.contextId = id;
this.ctxTime = Date.now();
logger.log(`上下文切换为:${id}`);
this.ctxPromise.resolve();
this.loadPromise = this.loadPromise.restart();
logger.log(`上下文切换为:${context.id}`);
this.chrome.contextCreateTime = Date.now();
this.emit(`CreatedContext`, origin);
}

@@ -154,13 +140,2 @@

/**
* 安全证书错误
* 由于没有返回关联id,导致线程阻塞,需要强制调用Promise解除阻塞
*/
'Security.certificateError'(data) {
for (let item of this.callbacks.values()) {
item.reject(data);
}
}
/**
* 框架加载完毕时触发,子框架优先级高于主框架

@@ -171,4 +146,5 @@ */

if (frameId === this.targetId) {
logger.success('页面加载完毕');
this.loadPromise.resolve();
}

@@ -182,12 +158,41 @@

await this.send('Page.handleJavaScriptDialog', { accept: false })
logger.log(`关闭${data.type}对话框`, data.message)
await this.send('Page.handleJavaScriptDialog', { accept: false });
logger.log(`关闭${data.type}对话框`, data.message);
}
/**
* 打开新窗口
* @param {*} param0
* 安全证书错误
* 由于没有返回关联id,导致线程阻塞,需要强制调用Promise解除阻塞
*/
'Page.windowOpen'() { }
'DOM.getContentQuads'() { }
'Security.certificateError'(data) {
for (const item of this.callbacks.values()) {
item.reject(data);
}
}
/**
* 订阅事件并等待触发
* @param {String} name 事件名称
* @param {Function} callback 回调
* @param {Number} timer 等待超时时间
*/
oncePromise(name, callback, timer = 10000) {
const promise = new timerPromise(timer);
this.once(name,async function (value) {
if (callback) {
await callback(value);
}
promise.resolve();
});
return promise;
}
}

@@ -194,0 +199,0 @@

{
"name": "auto-chrome",
"version": "1.0.7",
"version": "1.1.0",
"description": "使用Node.js操作Chrome或Chromium,高仿真的用户行为模拟器",

@@ -14,6 +14,5 @@ "main": "lib/",

"dependencies": {
"loggercc": "^1.3.1",
"timechain": "^1.3.2",
"ws": "^6.1.2",
"zpromise": "^1.5.0"
"loggercc": "^1.3.2",
"timechain": "^1.3.4",
"zpromise": "^1.5.3"
},

@@ -20,0 +19,0 @@ "directories": {

@@ -62,3 +62,3 @@ # auto-chrome

浏览器导航事件可分为可预测和不可预测两种,由于触发导航的方式非常多,通过鼠标、键盘、JS脚本等方式均可能触发未知的导航事件。如果导航切换时序不正确,会产生上下文消息错乱的bug。
浏览器导航事件可分为可预测和不可预测两种,由于触发导航的方式非常多,通过鼠标、键盘、JS脚本方式均可能触发未知的导航事件。如果导航切换时序不正确,会产生上下文消息错乱的bug。

@@ -73,13 +73,17 @@ 另外一种情况是由于url重定向,频繁的触发上下文切换,导致上下文错位。这种场景难以被察觉,也很难做预判。

对于不可预测导航,如按下Enter键、鼠标点击绑定了跳转事件的非链接元素触发导航行为。
* 键盘、鼠标事件绑定了跳转连接的非链接元素触发导航行为
autoChrome无法预判一个操作是否会触发导航,只能由用户来指定某个步骤是否需要等待导航结束后再继续执行。
* 导航可能在当前标签,也可能在新标签中加载
有时候会出现连用户自己也无法确定某个操作是否会触发导航,比如当点击一个动态元素时就会存在不确定性。
* 点击链接后出现一次或多次301重定向
以上情况中,无法准确预判一个操作是否会触发导航。autoChrome中通过循环探测的方式来实现自动导航,该方案的缺点是时效不高,如果是已知的静态导航,用户可以自定义导航行为。
有时候会出现连用户自己也无法确定某个操作是否会触发导航,比如当点击一个动态元素时会存在不确定性。
```js
// 等待导航键盘示例代码
await Promise.all([
page.keyboard.press("Enter"),
page.waitLoad()
chrome.keyboard.press("Enter"),
chrome.autoNav()
])

@@ -90,3 +94,3 @@

page.click("#input"),
page.waitLoad()
chrome.autoNav()
])

@@ -187,2 +191,9 @@ ```

### chrome.autoNav(time)
* `time` *Number* 等待超时时间
循环监测,自动导航
### chrome.close()

@@ -193,2 +204,3 @@

## class: Page

@@ -262,2 +274,6 @@

### page.clickNav(selector)
通过CSS选择器点击元素,内置导航
* `selector` *String* CSS选择器

@@ -308,7 +324,7 @@

### page.goBack()
### page.prev()
导航到上一个历史标签页
### page.goForward()
### page.next()

@@ -315,0 +331,0 @@ 导航到下一个历史标签页

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc