Skip to content

Commit

Permalink
Merge pull request #5828 from hashicorp/f-ui/ui-screenshots-script
Browse files Browse the repository at this point in the history
UI Screenshots script
  • Loading branch information
DingoEatingFuzz committed Jun 20, 2019
2 parents 6d889fe + e9731f0 commit 069455c
Show file tree
Hide file tree
Showing 10 changed files with 634 additions and 1 deletion.
15 changes: 15 additions & 0 deletions GNUmakefile
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,18 @@ help: ## Display this usage information
@echo ""
@echo "This host will build the following targets if 'make release' is invoked:"
@echo $(ALL_TARGETS) | sed 's/^/ /'

ui-screenshots:
@echo "==> Collecting UI screenshots..."
# Build the screenshots image if it doesn't exist yet
@if [[ "$$(docker images -q nomad-ui-screenshots 2> /dev/null)" == "" ]]; then \
docker build --tag="nomad-ui-screenshots" ./scripts/screenshots; \
fi
@docker run \
--rm \
--volume "$(shell pwd)/scripts/screenshots/screenshots:/screenshots" \
nomad-ui-screenshots

ui-screenshots-local:
@echo "==> Collecting UI screenshots (local)..."
@cd scripts/screenshots/src && SCREENSHOTS_DIR="../screenshots" node index.js
2 changes: 2 additions & 0 deletions scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
screenshots/screenshots/*
screenshots/src/node_modules
10 changes: 10 additions & 0 deletions scripts/screenshots/Dockerfile
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" ]

186 changes: 186 additions & 0 deletions scripts/screenshots/src/index.js
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`;
}
15 changes: 15 additions & 0 deletions scripts/screenshots/src/package.json
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"
}
}
90 changes: 90 additions & 0 deletions scripts/screenshots/src/utils.js
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
};
Loading

0 comments on commit 069455c

Please sign in to comment.