diff --git a/db/knex_migrations/2026-01-16-0000-add-screenshot-delay.js b/db/knex_migrations/2026-01-16-0000-add-screenshot-delay.js new file mode 100644 index 000000000..fa0829bdf --- /dev/null +++ b/db/knex_migrations/2026-01-16-0000-add-screenshot-delay.js @@ -0,0 +1,11 @@ +exports.up = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.integer("screenshot_delay").notNullable().unsigned().defaultTo(0); + }); +}; + +exports.down = function (knex) { + return knex.schema.alterTable("monitor", function (table) { + table.dropColumn("screenshot_delay"); + }); +}; diff --git a/server/model/monitor.js b/server/model/monitor.js index e01977133..2a23abd2f 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -1764,6 +1764,28 @@ class Monitor extends BeanModel { this.timeout = pingGlobalTimeout; } } + + if (this.type === "real-browser") { + // screenshot_delay validation + if (this.screenshot_delay !== undefined && this.screenshot_delay !== null) { + const delay = Number(this.screenshot_delay); + if (isNaN(delay) || delay < 0) { + throw new Error("Screenshot delay must be a non-negative number"); + } + + // Must not exceed 0.8 * timeout (page.goto timeout is interval * 1000 * 0.8) + const maxDelayFromTimeout = this.interval * 1000 * 0.8; + if (delay >= maxDelayFromTimeout) { + throw new Error(`Screenshot delay must be less than ${maxDelayFromTimeout}ms (0.8 × interval)`); + } + + // Must not exceed 0.5 * interval to prevent blocking next check + const maxDelayFromInterval = this.interval * 1000 * 0.5; + if (delay >= maxDelayFromInterval) { + throw new Error(`Screenshot delay must be less than ${maxDelayFromInterval}ms (0.5 × interval)`); + } + } + } } /** diff --git a/server/monitor-types/real-browser-monitor-type.js b/server/monitor-types/real-browser-monitor-type.js index 2b62a10a3..32fa36f1a 100644 --- a/server/monitor-types/real-browser-monitor-type.js +++ b/server/monitor-types/real-browser-monitor-type.js @@ -269,6 +269,11 @@ class RealBrowserMonitorType extends MonitorType { timeout: monitor.interval * 1000 * 0.8, }); + // Wait for additional time before taking screenshot if configured + if (monitor.screenshot_delay > 0) { + await page.waitForTimeout(monitor.screenshot_delay); + } + let filename = jwt.sign(monitor.id, server.jwtSecret) + ".png"; await page.screenshot({ diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 098c07286..ed354e3a0 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -1,8 +1,24 @@