Puppeteer – AWS Lambda 中使用 puppeteer 页面截图
     发布在:JavaScript      浏览:18      评论:0 条评论

之前公司项目,需要更换一个站点的后台系统,将整个站点迁移过去。

但是站点页面,包括各种新闻,超过8000多个。

人工进行新旧页面比对的话,emmmmm,大家也知道这种工程量。

于是决定做一个自动测试工具,使用 puppeteer 进行页面截图,然后对新旧站点页面截图比对,看新旧站点是否有样式缺失等情况。

并且为了节省服务器开支,我们需要将工具部署到 AWS 的 Lambda Functions 上,通过 API 进行访问,而截图都将存放在 AWS S3 中。

基于这个需求,我查阅了相关的资料,然后根据公司里其它前辈在 AWS Lambda 上的开发经验,实现了下面的功能。

(这里只提供 Puppeteer Screenshot in AWS Lambda 的实现代码,AWS API 会另外写文章)

Puppeteer: https://github.com/GoogleChrome/puppeteer

screenshot.js

const urlRequire = require('url');
const aws = require('aws-sdk');
const deviceDescriptors = require('puppeteer-core/DeviceDescriptiors'); // puppeteer 自带的一些设备参数 (Eg. Iphone 6)
const customDevices = require('./utils/custom_devices'); // 自定义的一些设备参数,我写在了最下面
const AwsLambdaHttpResponse = require('aws-lambda-http-response');
const chromium = require('chrome-aws-lambda');
const puppeteer = require('puppeteer-core');
// puppeteer launch chrome 的一些参数
const launchOptionForLambda = [
'--no-sandbox',
'--headless',
'--disable-gpu',
'--single-process',
'--disable-dev-shm-usage'
];
// Lambda handler
exports.handler = async (event, context, callback) => {
if (!event.url) {
// url 包括 host
console.log('URL was not provided.');
callback(null);
}
context.callbackWaitsForEmptyEventLoop = false;
// 是否是基准站点,即原站点
const baseline = event.baseline == null || event.baseline == 'true' || event.baseline == true ? 'true' : 'false';
// 测试哪些设备上的样式
const devices = event.devices == null || event.devices == '' ? null : event.devices;
try {
const result = await exports.run(event.url, baseline, devices);
new AwsLambdaHttpResponse({callback}).success({
body: {
baseline: baseline,
result: result
}
});
} catch (e) {
console.log('handler', e);
new AwsLambdaHttpResponse({callback}).error({
body: {
baseline: baseline,
result: e
}
});
}
};
// 功能实现
exports.run = async (url, baseline, devices) => {
// 将截图移动到 AWS S3
const fnMoveToS3 = function fnMoveToS3(img, url, baselise, device) {
return new Promise((resolve, reject) => {
const buf = new Buffer(img.replace(/^data:image\/\w+;base64,/, ''), 'base64');
const urlString = url;
const urlMap = urlRequire.parse(urlString, true).path;
const folder = urlMap.replace(/\//g, '>');
const s3 = new aws.s3({
apiVersion: '2006-03-01'
});
const fileName = (baseline === 'true' ? 'baseline_' : 'current_') + device.trim() + '_screenshot.png';
console.log('fileName', fileName);
return s3
.putObject({
Bucket: 'screenshot',
Key: folder + '/' + fileName,
Body: buf
})
.promise()
.then((data) => {
resolve(data);
})
.catch((e) => {
reject(e);
});
});
};
// 截图函数
const fnScreenshot = function fnScreenshot(device) {
return new Promise((resolve, reject) => {
const resObj = {};
const fnNavigate = async function fnNavigate(dv, url) {
console.log('url', url);
console.log('device', dv);
let img = '';
let browser = null;
try {
// 启动 browser
browser = await puppeteer.launch({
ignoreHTTPSErrors: true,
args: launchOptionForLambda,
executablePath: await chromium.executablePath,
headless: true
});
const page = await browser.newPage();
await page.setUserAgent(dv['userAgent']);
await page.setViewport({
width: dv['viewport']['width'],
height: db['viewport']['height'],
deviceScaleFactor: dv['viewport']['deviceScaleFactor'],
isMobile: dv['viewport']['isMobile'],
hasTouch: dv['viewport']['hasTouch'],
isLandscape: dv['viewport']['isLandscape']
});
// 如果页面需要权限验证
// const auth = new Buffer('username:password');
// await page.setExtraHTTPHeaders({
//      Authorization: 'Basic ' + auth
// });
await page.goto(url, {waitUntil: 'load', timeout: 8000});
img = await page.screenshot({
fullPage: true,
encoding: 'base64'
});
await page.close();
return fnMoveToS3(img, url, baseline, device)
.then((s3Result) => {
resObj[device] = s3Result;
console.log('0', resObj);
return resolve(resObj);
})
.catch((err) => {
console.log('1', err);
return reject(err);
});
} catch (e) {
console.log('2', e);
return reject(e);
} finally {
if (browser !== null) {
console.log('browser close finished');
await browser.close();
}
}
};
if (deviceDescriptors[device] != null) {
return fnNavigate(deviceDescriptors[device], url);
} else if (customDevices[device] != null) {
return fnNavigate(customDevices[device], url);
} else {
return reject(new Error('Not found this device'));
}
});
};
// 运行截图
const getScreenshots = devices.map(fnScreenshot);
const results = Promise.all(getScreenshots);
// 返回结果
return results
.then((res) => {
return res;
})
.catch((err) => {
throw err;
});
};

custom_devices.js

module.exports = [
{
name: 'Desktop 1920x1080',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
},
{
name: 'width: 768 x height: 1024',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 1024,
height: 768,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
},
{
name: 'width: 1024 x height: 1366',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 1366,
height: 1024,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
},
{
name: 'width: 375 x height: 812',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 375,
height: 812,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
},
{
name: 'width: 750 x height: 1334',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 1334,
height: 750,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
},
{
name: 'width: 1242 x height: 2688',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 1242,
height: 2688,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
},
{
name: 'width: 1920 x height: 1080',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 1920,
height: 1080,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
},
{
name: 'width: 1080 x height: 2160',
userAgent:
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)' +
' Chrome/68.0.3440.75 Safari/537.36',
viewport: {
width: 1080,
height: 2160,
deviceScaleFactor: 1,
isMobile: false,
hasTouch: false,
isLandscape: false
}
}
];
for (const device of module.exports) {
module.exports[device.name] = device;
}
Responses