Skip to content

Commit 46f7d52

Browse files
committed
v0.1
0 parents  commit 46f7d52

File tree

15 files changed

+1389
-0
lines changed

15 files changed

+1389
-0
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules/
2+
chromium/

LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Playwright自动化脚本加载器
2+
3+
## 简介
4+
5+
一个基于`node.js``Playwright`的自动化脚本加载器,方便分享自己的脚本给他人使用。
6+
7+
用户下载本项目发行包后,可以像安装插件一样导入`Playwright`自动化脚本,避免配置开发环境。
8+
9+
## 初始化
10+
11+
先安装依赖
12+
13+
```sh
14+
npm install
15+
```
16+
17+
然后下载`chromium`,并且必须是编译了编解码器的版本,否则浏览器无法播放视频!
18+
19+
可以到[https://chromium.woolyss.com/](https://chromium.woolyss.com/)下载,绿底表示稳定版本,含`all-codecs+`的才能播放视频
20+
21+
找到合适版本点击`Archive`即可下载
22+
23+
解压后重命名文件夹为`chromium`,移动到项目根目录,确保有`chromium\chrome.exe`
24+
25+
## 启动
26+
27+
终端里面运行`npm run start`,或`node index.js`,即可启动脚本加载器。
28+
29+
## 发行
30+
31+
初始化后额外下载一个node.js的预编译二进制版本,放到项目目录
32+
33+
然后写一个启动脚本即可
34+
35+
比如Windows下可以写一个`start.bat`脚本作为启动脚本,内容为`.\yourpath\node.exe index.js`即可
36+
37+
最后打包给用户,告知用户通过启动脚本启动即可
38+
39+
## 使用
40+
41+
用户把插件放到`plugins`目录下。启动加载器后,用户可以选择一个插件来运行。
42+
43+
## 插件开发
44+
45+
插件开发需要遵循以下规范:
46+
47+
* `plugins`目录下放置插件,一个插件对应一个目录
48+
* 插件目录下必须有`info.json`保存插件信息(`info.json`是判断文件夹是否为插件文件夹的依据),`info.json`定义插件的`插件名称``解释说明``作者``版本号`信息
49+
* 可选`icon.png`作为插件的图标,如果没有则显示默认图标
50+
* 可选`setting.json`存储插件的设置信息,可选`setting.html`用于插件的设置页面,设置页面先于插件加载,用户设置完成后才会加载插件
51+
* `index.js`作为插件的程序入口,加载插件后执行导出的默认函数
52+
53+
`info.json`格式如下:
54+
55+
```json
56+
{
57+
"name": "插件名称",
58+
"description": "插件说明",
59+
"author": "作者名称",
60+
"version": "版本号"
61+
}
62+
```
63+
64+
`index.js`必须提供如下格式的入口函数:
65+
66+
```js
67+
export default async function main(page, context, setting) {
68+
// page是创建好的新页面,context是浏览器上下文对象
69+
// 如果有设置文件,setting是一个保存了插件设置信息的对象
70+
// 如果没有设置文件,setting为null
71+
// 下面可自由编写业务逻辑
72+
await page.goto('https://xxxx'); // 跳转网址示例
73+
}
74+
```
75+
76+
如果提供`setting.html`,那么加载器会在加载插件前打开设置页面,用户可以根据设置信息来调整插件的运行参数
77+
78+
通过下面的方法获取之前的旧设置:
79+
80+
```js
81+
// 监听来自插件加载器的消息
82+
window.addEventListener('message', (msg) => {
83+
if (msg.data?.type === 'initialize') {
84+
const data = msg.data.data;
85+
// data即为setting.json文件的内容,为js对象
86+
// 如果没有setting.json文件,data为null
87+
}
88+
});
89+
```
90+
91+
通过下面的方法发送新的设置:
92+
93+
```js
94+
// 向插件加载器发送setting json,传入js对象
95+
function sendSetting2Loader(data) {
96+
console.log(JSON.stringify({ type: 'pluginSetting', data: data }));
97+
}
98+
```
99+
100+
发送新设置后,会关闭设置页面,创建新页面后调用`index.js`的入口函数开始执行插件内容,并传入新的设置对象。如果发送null则会中断插件加载
101+
102+
本项目`plugins`有相关示例可以参考
103+
104+
## 许可证
105+
106+
本程序遵循 [GPL-3.0-only](https://opensource.org/license/gpl-3-0/)许可证。
107+
108+
本程序仅供学习研究使用,严禁用于商业用途!
109+
110+
> 注意:由于GPL协议的强约束性,如果您将本项目的代码用于您的商业项目,会导致您商业项目的所有代码被迫全部以相同协议开源
111+
112+
本项目许可证的具体内容详见项目根目录下的LICENSE文件
113+
114+
您也可访问[GNU的网站](https://www.gnu.org/licenses/)获取更多有关GPL许可证以及自由软件运动的相关信息

images/pluginDefaultIcon.png

15.9 KB
Loading

index.js

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { startBrowser } from './src/starter.js';
2+
import { scanPlugins } from './src/pluginsScanner.js';
3+
import fs from 'fs/promises';
4+
5+
// 发送消息给页面
6+
async function sendMessage2Page(page, type, data) {
7+
const msgObject = { type, ...data };
8+
await page.evaluate((msg) => {
9+
window.postMessage(msg, '*');
10+
}, msgObject);
11+
}
12+
13+
// 加载插件
14+
async function loadPlugin(pluginName, pluginFolderName, pluginSettingHtmlFileURL, browser, indexcontext, indexpage) {
15+
// js对象形式的插件设置数据
16+
let settingData = null;
17+
18+
// 处理插件自定义的设置页面
19+
if (pluginSettingHtmlFileURL) {
20+
console.log(`等待用户设置插件 ${pluginName}`);
21+
22+
// 在加载器的上下文打开插件设置对话框
23+
const newPagePromise = indexcontext.waitForEvent('page');
24+
await indexpage.evaluate(() => {
25+
window.open('about:blank', '_blank', 'left=200,top=200');
26+
});
27+
const popup = await newPagePromise;
28+
29+
// 设置加载设置页面
30+
await popup.goto(pluginSettingHtmlFileURL);
31+
32+
// 发送原本的设置数据
33+
let settingDataStr = null; // json字符串版本
34+
try {
35+
// 读取文件内容并且解析为对象
36+
settingDataStr = await fs.readFile(`./plugins/${pluginFolderName}/setting.json`, 'utf-8');
37+
settingData = JSON.parse(settingDataStr);
38+
} catch (e) {
39+
// 忽略错误
40+
}
41+
await sendMessage2Page(popup, 'initialize', { data: settingData });
42+
43+
// 是否取消加载插件
44+
let isCancel = false
45+
46+
// 监听来自设置页面的消息
47+
popup.on('console', async logstr => {
48+
let data = null
49+
let hasData = false
50+
try {
51+
const msgObj = JSON.parse(logstr.text());
52+
if (msgObj?.type === 'pluginSetting') {
53+
data = msgObj.data;
54+
hasData = true;
55+
}
56+
} catch (e) {
57+
// 忽略非json输出
58+
}
59+
if (hasData) {
60+
if (data) {
61+
const newSettingStrJSON = JSON.stringify(data);
62+
// 保存设置到json文件
63+
if (settingDataStr !== newSettingStrJSON) {
64+
// 不相等才保存
65+
await fs.writeFile(`./plugins/${pluginFolderName}/setting.json`, newSettingStrJSON, 'utf-8');
66+
console.log(`更新了插件 ${pluginName} 的设置`);
67+
settingData = data;
68+
}
69+
// 设置完成,放出关闭页面信号
70+
await popup.evaluate(() => {
71+
document.body.innerHTML = '<h1 id="SettingOK">正在设置中...</h1>';
72+
});
73+
} else {
74+
// 取消插件加载
75+
isCancel = true;
76+
await popup.evaluate(() => {
77+
document.body.innerHTML = '<h1 id="SettingOK">正在取消...</h1>';
78+
});
79+
}
80+
}
81+
});
82+
// 等待用户设置完成,不限制时间
83+
await popup.waitForSelector('#SettingOK', { timeout: 0 });
84+
popup.close(); // 关闭对话框
85+
if (isCancel) {
86+
// 取消加载插件
87+
console.log(`用户取消加载插件 ${pluginName}`);
88+
return;
89+
}
90+
}
91+
92+
// 正式加载插件
93+
console.log(`加载插件 ${pluginName}`);
94+
95+
// 创建上下文
96+
const context = await browser.newContext();
97+
98+
// 创建新页面
99+
const page = await context.newPage();
100+
101+
// 根据插件名称动态导入插件
102+
const plugin = await import(`./plugins/${pluginFolderName}/index.js`);
103+
104+
// 调用插件中的 main 函数
105+
await plugin.default(page, context, settingData);
106+
107+
// 插件结束运行
108+
console.log(`插件结束运行 ${pluginName}`);
109+
}
110+
111+
(async () => {
112+
// 启动浏览器
113+
const { browser, context, page } = await startBrowser();
114+
115+
// 发送插件列表
116+
sendMessage2Page(page, 'initialize', { plugins: await scanPlugins('./plugins') });
117+
118+
// 监听来自 GUI 的消息
119+
page.on('console', async message => {
120+
// 保证一定是JSON格式的消息
121+
const data = JSON.parse(message.text());
122+
if (data?.type === 'loadPlugin') {
123+
await loadPlugin(data.name, data.folderName, data.settingHtmlFileURL, browser, context, page);
124+
}
125+
});
126+
127+
// 等待GUI告知关闭
128+
await page.waitForSelector('#NowExit', { timeout: 0 });
129+
130+
// 关闭浏览器
131+
await browser.close();
132+
})();

package-lock.json

Lines changed: 60 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"name": "playwright_automation_script_loader",
3+
"version": "1.0.0",
4+
"main": "index.js",
5+
"scripts": {
6+
"start": "node index.js"
7+
},
8+
"type": "module",
9+
"author": "XY0797",
10+
"license": "GPLv3",
11+
"description": "Playwright自动化脚本加载器",
12+
"dependencies": {
13+
"playwright": "^1.46.1"
14+
}
15+
}
26.3 KB
Loading
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export default async function main(page, context, setting) {
2+
await page.goto('https://www.baidu.com/');
3+
await page.getByRole('link', { name: '登录' }).click();
4+
await page.getByPlaceholder('手机号/用户名/邮箱').click();
5+
await page.getByPlaceholder('手机号/用户名/邮箱').fill(setting.username);
6+
await page.getByPlaceholder('密码').click();
7+
await page.getByPlaceholder('密码').fill('456');
8+
await page.getByRole('checkbox', { name: '阅读并接受' }).check(setting.password);
9+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "百度登录插件",
3+
"description": "自动输入账号密码,勾选同意协议,需用户自己点击登录按钮",
4+
"author": "XY0797",
5+
"version": "1.0"
6+
}

0 commit comments

Comments
 (0)