-
Notifications
You must be signed in to change notification settings - Fork 2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #5828 from hashicorp/f-ui/ui-screenshots-script
UI Screenshots script
- Loading branch information
Showing
10 changed files
with
634 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
screenshots/screenshots/* | ||
screenshots/src/node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
FROM buildkite/puppeteer:v1.15.0 | ||
|
||
COPY src . | ||
|
||
ENV EMBER_HOST=http://host.docker.internal:4200 | ||
|
||
RUN npm install | ||
|
||
CMD [ "node", "index.js" ] | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
const puppeteer = require("puppeteer"); | ||
const { | ||
capture, | ||
wait, | ||
error, | ||
click, | ||
clickJob, | ||
clickTab, | ||
clickMainMenu, | ||
clickX | ||
} = require("./utils"); | ||
|
||
const HOST = process.env.EMBER_HOST || "http://localhost:4200"; | ||
console.log(`Using host ${HOST}...`); | ||
|
||
const ANSI_YELLOW = "\x1b[33m%s\x1b[0m"; | ||
|
||
(async () => { | ||
const startTime = Date.now(); | ||
console.log("Preparing puppeteer..."); | ||
|
||
// Create a new browser and tab | ||
const browser = await puppeteer.launch({ | ||
// Docker related chrome flags | ||
args: [ | ||
"--no-sandbox", | ||
"--disable-setuid-sandbox", | ||
"--disable-dev-shm-usage" | ||
] | ||
}); | ||
const page = await browser.newPage(); | ||
|
||
// Make sure the page is 4K is high-dpi scaling | ||
page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 }); | ||
console.log("Loading Nomad UI..."); | ||
console.log( | ||
ANSI_YELLOW, | ||
"\n!! Make sure to use the everyFeature Mirage scenario !!\n" | ||
); | ||
|
||
try { | ||
await page.goto(`${HOST}/ui/`); | ||
} catch (err) { | ||
await error( | ||
browser, | ||
"Could not load the Nomad UI. Is the Ember server running?" | ||
); | ||
} | ||
|
||
// Give Mirage a chance to settle | ||
console.log("Waiting for Mirage..."); | ||
await wait(5000); | ||
console.log("Starting capture sequence!\n"); | ||
|
||
// DEBUG: log the URL on all navigations | ||
monitorURL(page); | ||
|
||
await capture(page, "jobs-list"); | ||
|
||
await clickJob(page, "service"); | ||
await capture(page, "job-detail-service"); | ||
await page.goBack(); | ||
|
||
await clickJob(page, "batch"); | ||
await capture(page, "job-detail-batch"); | ||
await page.goBack(); | ||
|
||
await clickJob(page, "system"); | ||
await capture(page, "job-detail-system"); | ||
await page.goBack(); | ||
|
||
await clickJob(page, "periodic"); | ||
await capture(page, "job-detail-periodic"); | ||
await click(page, "tr.job-row"); | ||
await capture(page, "job-detail-periodic-child"); | ||
await page.goBack(); | ||
await page.goBack(); | ||
|
||
await clickJob(page, "parameterized"); | ||
await capture(page, "job-detail-parameterized"); | ||
await click(page, "tr.job-row"); | ||
await capture(page, "job-detail-parameterized-child"); | ||
await page.goBack(); | ||
await page.goBack(); | ||
|
||
await clickJob(page, "service"); | ||
|
||
await clickTab(page, "Definition"); | ||
await capture(page, "job-detail-tab-definition"); | ||
await page.click(".boxed-section .button.is-light.is-compact.pull-right"); | ||
await capture(page, "job-detail-tab-definition-editing"); | ||
|
||
await clickTab(page, "Versions"); | ||
await capture(page, "job-detail-tab-versions"); | ||
await page.click(".timeline-object .button.is-light.is-compact.pull-right"); | ||
await capture(page, "job-detail-tab-versions-expanded"); | ||
|
||
await clickTab(page, "Deployments"); | ||
await capture(page, "job-detail-tab-deployments"); | ||
await page.click(".timeline-object .button.is-light.is-compact.pull-right"); | ||
await capture(page, "job-detail-tab-deployments-expanded"); | ||
|
||
await clickTab(page, "Allocations"); | ||
await capture(page, "job-detail-tab-allocations"); | ||
|
||
await clickTab(page, "Evaluations"); | ||
await capture(page, "job-detail-tab-evaluations"); | ||
|
||
await clickMainMenu(page, "Jobs"); | ||
await page.click(".toolbar-item .button.is-primary"); | ||
await capture(page, "job-run-empty"); | ||
// Fill in the code editor somehow | ||
// Capture the plan stage | ||
|
||
await clickMainMenu(page, "Jobs"); | ||
await clickJob(page, "service"); | ||
await click(page, ".task-group-row"); | ||
await capture(page, "task-group"); | ||
|
||
await clickMainMenu(page, "Jobs"); | ||
await clickJob(page, "service"); | ||
|
||
const allocCount = await page.$$eval(".allocation-row", s => s.length); | ||
for (let i = 1; i <= allocCount; i++) { | ||
await click(page, `.allocation-row:nth-of-type(${i}) a.is-primary`); | ||
await capture(page, `allocation-${i}`); | ||
await page.goBack(); | ||
await wait(2000); | ||
} | ||
|
||
await click(page, ".allocation-row a.is-primary"); | ||
await click(page, ".task-row"); | ||
await capture(page, "task-detail"); | ||
|
||
await clickTab(page, "Logs"); | ||
await capture(page, "task-logs"); | ||
|
||
await clickMainMenu(page, "Clients"); | ||
await capture(page, "clients-list"); | ||
|
||
const clientCount = await page.$$eval(".client-node-row", s => s.length); | ||
for (let i = 1; i <= clientCount; i++) { | ||
await click(page, `.client-node-row:nth-of-type(${i})`); | ||
await capture(page, `client-detail-${i}`); | ||
await page.goBack(); | ||
await wait(500); | ||
} | ||
|
||
await clickMainMenu(page, "Servers"); | ||
await capture(page, "servers-list"); | ||
|
||
await click(page, `.server-agent-row:nth-of-type(2)`); | ||
await capture(page, "server-detail"); | ||
|
||
await clickX(page, '//a[contains(text(), "ACL Tokens")]'); | ||
await capture(page, "acl-tokens"); | ||
|
||
console.log(`All done! ${humanDuration(Date.now() - startTime)}`); | ||
process.exit(); | ||
})(); | ||
|
||
async function* watchURL(page) { | ||
while (true) { | ||
await page.waitForNavigation(); | ||
yield page.url(); | ||
} | ||
} | ||
|
||
async function monitorURL(page) { | ||
for await (let url of watchURL(page)) { | ||
console.log(`=> ${url}`); | ||
} | ||
} | ||
|
||
function humanDuration(duration) { | ||
const ms = duration % 1000; | ||
const s = Math.floor((duration / 1000) % 60); | ||
const m = Math.floor(duration / 1000 / 60); | ||
|
||
const fs = s < 10 ? `0${s}` : `${s}`; | ||
const fms = ms < 10 ? `00${ms}` : ms < 100 ? `0${ms}` : `${ms}`; | ||
|
||
if (m) return `${m}m ${fs}s ${fms}ms`; | ||
else if (s) return `${fs}s ${fms}ms`; | ||
return `${fms}ms`; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "nomad-ui-screenshots", | ||
"version": "1.0.0", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"keywords": [], | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"puppeteer": "^1.16.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
async function error(browser, message = "Something went wrong.") { | ||
console.error(message); | ||
await browser.close(); | ||
process.exit(); | ||
} | ||
|
||
async function capture(page, name, options = {}) { | ||
console.log(`Capturing ${name}`); | ||
const dir = process.env.SCREENSHOTS_DIR || "screenshots"; | ||
await page.screenshot( | ||
Object.assign({ path: `${dir}/${name}.png`, fullPage: true }, options) | ||
); | ||
} | ||
|
||
async function wait(time) { | ||
return new Promise(resolve => { | ||
setTimeout(resolve, time); | ||
}); | ||
} | ||
|
||
async function click(page, selector, options) { | ||
const [response] = await Promise.all([ | ||
page.waitForNavigation(), | ||
page.click(selector, options) | ||
]); | ||
|
||
// Allow for render | ||
await wait(500); | ||
|
||
return response; | ||
} | ||
|
||
async function clickX(page, path) { | ||
const [element] = await page.$x(path); | ||
const [response] = await Promise.all([ | ||
page.waitForNavigation(), | ||
element.click() | ||
]); | ||
|
||
// Allow for render | ||
await wait(500); | ||
|
||
return response; | ||
} | ||
|
||
async function clickJob(page, type) { | ||
let jobIndex = await page.$$eval( | ||
"tr.job-row", | ||
(rows, type) => | ||
rows.findIndex( | ||
row => row.querySelector("td:nth-child(3)").textContent.trim() === type | ||
), | ||
type | ||
); | ||
jobIndex++; | ||
|
||
await clickX(page, `//tr[contains(@class, "job-row")][${jobIndex}]`); | ||
} | ||
|
||
async function clickTab(page, label) { | ||
let tabIndex = await page.$$eval( | ||
".tabs.is-subnav a", | ||
(tabs, label) => tabs.findIndex(tab => tab.textContent.trim() === label), | ||
label | ||
); | ||
tabIndex++; | ||
|
||
await clickX( | ||
page, | ||
`//div[contains(@class, "is-subnav")]//ul//li[${tabIndex}]//a` | ||
); | ||
} | ||
|
||
async function clickMainMenu(page, label) { | ||
await clickX( | ||
page, | ||
`//div[contains(@class, "page-column is-left")]//a[contains(text(), "${label}")]` | ||
); | ||
} | ||
|
||
module.exports = { | ||
error, | ||
capture, | ||
wait, | ||
click, | ||
clickX, | ||
clickJob, | ||
clickTab, | ||
clickMainMenu | ||
}; |
Oops, something went wrong.