AWS CloudWatch Canary(Node.js Puppeteer):优化XML文本校验错误提示
通过AWS CloudWatch Canary的GUI生成代码并适配后,用于检测返回XML格式内容的网页,目标是校验ABC标签的value属性中是否存在特定字符串。当前代码在字符串存在时能正常通过,但不存在时会抛出超时错误,而非明确的“未找到”提示,错误信息如下:
TimeoutError: Waiting for selector
.//ABC[@value='searchingforthistext']failed: Waiting failed: 5000ms exceeded Stack: TimeoutError: Waiting for selector.//ABC[@value='searchingforthistext']failed: Waiting failed: 5000ms exceeded at Timeout.(/opt/nodejs/node_modules/puppeteer-core/lib/cjs/puppeteer/common/WaitTask.js:65:32) at listOnTimeout (node:internal/timers:559:17) at processTimers (node:internal/timers:502:7)
目标XML格式:
<Description> <XYZ code="100" label="some text here" value="22"/> <ABC code="110" label="some text here" value="searchingforthistext"/> </Description>
现有Canary代码:
const synthetics = require('Synthetics'); const log = require('SyntheticsLogger'); const syntheticsConfiguration = synthetics.getConfiguration(); const flowBuilderBlueprint = async function () { // INSERT URL here let url = "https://something.com/mypage"; syntheticsConfiguration.setConfig({ includeRequestHeaders: true, // Enable if headers should be displayed in HAR includeResponseHeaders: true, // Enable if headers should be displayed in HAR restrictedHeaders: [], // Value of these headers will be redacted from logs and reports restrictedUrlParameters: [] // Values of these url parameters will be redacted from logs and reports }); let page = await synthetics.getPage(); // Navigate to the initial url await synthetics.executeStep('navigateToUrl', async function (timeoutInMillis = 30000) { await page.goto(url, {waitUntil: ['load', 'networkidle0'], timeout: timeoutInMillis}); }); // Execute customer steps await synthetics.executeStep('verifyText', async function () { const exists = await page.waitForXPath("//ABC[@value='searchingforthistext']", { timeout: 5000 } ); if (exists) { console.log('text found') } else { console.log('text not found') } }); }; exports.handler = async () => { return await flowBuilderBlueprint(); };
问题核心是page.waitForXPath的特性:当XPath匹配不到元素时,它会直接抛出超时错误,不会进入后续的if-else判断。要实现明确的结果提示,可通过以下方式修改:
修改思路
- 改用
page.$x直接查询元素,通过返回数组的长度判断是否匹配,避免超时等待 - 使用CloudWatch专用日志工具
SyntheticsLogger记录结果,确保日志能被正确采集 - 未找到目标时主动调用
synthetics.addExecutionError标记Canary失败,生成直观的错误报告
修改后的完整代码
const synthetics = require('Synthetics'); const log = require('SyntheticsLogger'); const syntheticsConfiguration = synthetics.getConfiguration(); const flowBuilderBlueprint = async function () { let url = "https://something.com/mypage"; syntheticsConfiguration.setConfig({ includeRequestHeaders: true, includeResponseHeaders: true, restrictedHeaders: [], restrictedUrlParameters: [] }); let page = await synthetics.getPage(); await synthetics.executeStep('navigateToUrl', async function (timeoutInMillis = 30000) { await page.goto(url, {waitUntil: ['load', 'networkidle0'], timeout: timeoutInMillis}); }); await synthetics.executeStep('verifyTargetValue', async function () { // 直接查询匹配的元素数组 const matchedElements = await page.$x("//ABC[@value='searchingforthistext']"); if (matchedElements.length > 0) { log.info('目标字符串已找到,校验通过'); } else { log.error('未找到目标字符串,校验失败'); // 主动标记Canary执行失败,自定义错误信息 synthetics.addExecutionError('ABC标签的value属性中未找到目标字符串'); } }); }; exports.handler = async () => { return await flowBuilderBlueprint(); };
关键修改说明
page.$x的优势:直接返回匹配的元素数组,不会抛出异常,能立即判断结果,避免不必要的超时等待- 日志规范:用
log.info/log.error代替console.log,确保日志能在CloudWatch Synthetics控制台中正常显示 - 失败标记:调用
synthetics.addExecutionError会明确将Canary状态标记为失败,同时在报告中展示自定义错误信息,比默认超时错误更直观
内容的提问来源于stack exchange,提问作者Martin W

