mirror of
https://github.com/chanzuckerberg/cellxgene.git
synced 2026-09-30 17:28:12 +08:00
* Add smoke test for annotations features
* Do not save during annotations tests
* Fix botched rebase in dev guidelines
* Revert "Do not save during annotations tests"
This reverts commit f0bd970bb2.
* Respond to feedback from @bkmartinjr
266 lines
8.4 KiB
JavaScript
266 lines
8.4 KiB
JavaScript
import {DEBUG, DEV} from "./config";
|
|
import puppeteer from "puppeteer";
|
|
|
|
export const puppeteerUtils = puppeteerPage => ({
|
|
async waitByID(testid, props = {}) {
|
|
return await puppeteerPage.waitForSelector(
|
|
`[data-testid='${testid}']`,
|
|
props
|
|
);
|
|
},
|
|
|
|
async waitByClass(testclass, props = {}) {
|
|
return await puppeteerPage.waitForSelector(
|
|
`[data-testclass='${testclass}']`,
|
|
props
|
|
);
|
|
},
|
|
|
|
async waitForAllByIds(testids, props = {}) {
|
|
await Promise.all(
|
|
testids.map(testid =>
|
|
puppeteerPage.waitForSelector(`[data-testid='${testid}']`)
|
|
)
|
|
);
|
|
},
|
|
|
|
async getAllByClass(testclass, props = {}) {
|
|
const elements = await puppeteerPage.$$eval(
|
|
`[data-testclass=${testclass}]`,
|
|
els => {
|
|
return els.map(el => {
|
|
return el.dataset.testid;
|
|
});
|
|
}
|
|
);
|
|
return elements;
|
|
},
|
|
|
|
async typeInto(testid, text) {
|
|
// only works for text without special characters
|
|
await this.waitByID(testid);
|
|
const selector = `[data-testid='${testid}']`;
|
|
// type ahead can be annoying if you don't pause before you type
|
|
await puppeteerPage.click(selector);
|
|
await puppeteerPage.waitFor(200);
|
|
await puppeteerPage.type(selector, text);
|
|
},
|
|
|
|
async clearInputAndTypeInto(testid, text) {
|
|
await this.waitByID(testid);
|
|
const selector = `[data-testid='${testid}']`;
|
|
// only works for text without special characters
|
|
// type ahead can be annoying if you don't pause before you type
|
|
await puppeteerPage.click(selector);
|
|
await puppeteerPage.waitFor(200);
|
|
// select all
|
|
|
|
await puppeteerPage.click(selector, { clickCount: 3 });
|
|
await puppeteerPage.keyboard.type("Backspace");
|
|
await puppeteerPage.type(selector, text);
|
|
},
|
|
|
|
async clickOn(testid) {
|
|
await this.waitByID(testid);
|
|
await puppeteerPage.click(`[data-testid='${testid}']`);
|
|
await puppeteerPage.waitFor(50);
|
|
},
|
|
|
|
async hoverOn(testid) {
|
|
await this.waitByID(testid);
|
|
await puppeteerPage.hover(`[data-testid='${testid}']`);
|
|
await puppeteerPage.waitFor(50);
|
|
},
|
|
|
|
async getOneElementInnerHTML(selector) {
|
|
await puppeteerPage.waitForSelector(selector);
|
|
let text = await puppeteerPage.$eval(selector, el => el.innerHTML);
|
|
return text;
|
|
},
|
|
|
|
async getOneElementInnerText(selector) {
|
|
await puppeteerPage.waitForSelector(selector);
|
|
let text = await puppeteerPage.$eval(selector, el => el.innerText);
|
|
return text;
|
|
}
|
|
});
|
|
|
|
export const cellxgeneActions = puppeteerPage => ({
|
|
async drag(testid, start, end, lasso = false) {
|
|
const layout = await puppeteerUtils(puppeteerPage).waitByID(testid);
|
|
const elBox = await layout.boxModel();
|
|
const x1 = elBox.content[0].x + start.x;
|
|
const x2 = elBox.content[0].x + end.x;
|
|
const y1 = elBox.content[0].y + start.y;
|
|
const y2 = elBox.content[0].y + end.y;
|
|
await puppeteerPage.mouse.move(x1, y1);
|
|
await puppeteerPage.mouse.down();
|
|
if (lasso) {
|
|
await puppeteerPage.mouse.move(x2, y1);
|
|
await puppeteerPage.mouse.move(x2, y2);
|
|
await puppeteerPage.mouse.move(x1, y2);
|
|
await puppeteerPage.mouse.move(x1, y1);
|
|
} else {
|
|
await puppeteerPage.mouse.move(x2, y2);
|
|
}
|
|
await puppeteerPage.mouse.up();
|
|
},
|
|
|
|
async clickOnCoordinate(testid, coord) {
|
|
const layout = await puppeteerUtils(puppeteerPage).waitByID(testid);
|
|
const elBox = await layout.boxModel();
|
|
const x = elBox.content[0].x + coord.x;
|
|
const y = elBox.content[0].y + coord.y;
|
|
await puppeteerPage.mouse.click(x, y);
|
|
},
|
|
|
|
async getAllHistograms(testclass, testids) {
|
|
const histTestIds = testids.map(tid => `histogram-${tid}`);
|
|
// these load asynchronously, so we need to wait for each histogram individually
|
|
await puppeteerUtils(puppeteerPage).waitForAllByIds(histTestIds);
|
|
const allHistograms = await puppeteerUtils(puppeteerPage).getAllByClass(
|
|
testclass
|
|
);
|
|
return allHistograms.map(hist =>
|
|
hist.substr("histogram_".length, hist.length)
|
|
);
|
|
},
|
|
|
|
async getAllCategoriesAndCounts(category) {
|
|
await puppeteerUtils(puppeteerPage).waitByClass("categorical-row");
|
|
const categories = await puppeteerPage.$$eval(
|
|
`[data-testid="category-${category}"] [data-testclass='categorical-row']`,
|
|
els => {
|
|
let result = {};
|
|
els.forEach(el => {
|
|
const cat = el.querySelector("[data-testclass='categorical-value']")
|
|
.innerText;
|
|
const count = el.querySelector(
|
|
"[data-testclass='categorical-value-count']"
|
|
).innerText;
|
|
result[cat] = count;
|
|
});
|
|
return result;
|
|
}
|
|
);
|
|
return categories;
|
|
},
|
|
|
|
async cellSet(num) {
|
|
await puppeteerUtils(puppeteerPage).clickOn(`cellset-button-${num}`);
|
|
return await puppeteerUtils(puppeteerPage).getOneElementInnerText(
|
|
`[data-testid='cellset-count-${num}']`
|
|
);
|
|
},
|
|
|
|
async resetCategory(category) {
|
|
const checkboxId = `${category}:category-select`;
|
|
await puppeteerUtils(puppeteerPage).waitByID(checkboxId);
|
|
const checkedPseudoclass = await puppeteerPage.$eval(
|
|
`[data-testid='${checkboxId}']`,
|
|
el => {
|
|
return el.matches(":checked");
|
|
}
|
|
);
|
|
if (!checkedPseudoclass) {
|
|
await puppeteerUtils(puppeteerPage).clickOn(checkboxId);
|
|
}
|
|
try {
|
|
const categoryRow = await puppeteerUtils(puppeteerPage).waitByID(
|
|
`${category}:category-expand`
|
|
);
|
|
const isExpanded = await categoryRow.$(
|
|
"[data-testclass='category-expand-is-expanded']"
|
|
);
|
|
if (isExpanded) {
|
|
await puppeteerUtils(puppeteerPage).clickOn(
|
|
`${category}:category-expand`
|
|
);
|
|
}
|
|
} catch {}
|
|
},
|
|
|
|
async calcCoordinate(testid, xAsPercent, yAsPercent) {
|
|
const el = await puppeteerUtils(puppeteerPage).waitByID(testid);
|
|
const size = await el.boxModel();
|
|
return {
|
|
x: Math.floor(size.width * xAsPercent),
|
|
y: Math.floor(size.height * yAsPercent)
|
|
}
|
|
},
|
|
|
|
async calcDragCoordinates(testid, coordinateAsPercent) {
|
|
const coords = {
|
|
start: await this.calcCoordinate(testid, coordinateAsPercent.x1, coordinateAsPercent.y1),
|
|
end: await this.calcCoordinate(testid, coordinateAsPercent.x2, coordinateAsPercent.y2)
|
|
};
|
|
return coords;
|
|
},
|
|
|
|
async selectCategory(category, values, reset = true) {
|
|
if (reset) await this.resetCategory(category);
|
|
await puppeteerUtils(puppeteerPage).clickOn(`${category}:category-expand`);
|
|
await puppeteerUtils(puppeteerPage).clickOn(`${category}:category-select`);
|
|
for (const val of values) {
|
|
await puppeteerUtils(puppeteerPage).clickOn(
|
|
`categorical-value-select-${category}-${val}`
|
|
);
|
|
}
|
|
},
|
|
|
|
async expandCategory(category) {
|
|
const expand = await puppeteerUtils(puppeteerPage).waitByID(`${category}:category-expand`);
|
|
const expandArrow = await expand.$("[data-testclass='category-expand-is-not-expanded']");
|
|
await expandArrow.click();
|
|
},
|
|
|
|
async reset() {
|
|
await puppeteerUtils(puppeteerPage).clickOn("reset");
|
|
// loading state never actually happens, reset is too fast
|
|
await page.waitFor(200);
|
|
},
|
|
|
|
async clip(min = 0, max = 100) {
|
|
await puppeteerUtils(puppeteerPage).clickOn("visualization-settings");
|
|
await puppeteerUtils(puppeteerPage).clearInputAndTypeInto(
|
|
"clip-min-input",
|
|
min
|
|
);
|
|
await puppeteerUtils(puppeteerPage).clearInputAndTypeInto(
|
|
"clip-max-input",
|
|
max
|
|
);
|
|
await puppeteerUtils(puppeteerPage).clickOn("clip-commit");
|
|
}
|
|
});
|
|
|
|
export async function setupTestBrowser(browserViewport) {
|
|
const browserParams = DEV
|
|
? { headless: false, slowMo: 5 }
|
|
: DEBUG
|
|
? { headless: false, slowMo: 100, devtools: true }
|
|
: {};
|
|
const browser = await puppeteer.launch(browserParams);
|
|
const page = await browser.newPage();
|
|
await page.setViewport(browserViewport);
|
|
if (DEV || DEBUG) {
|
|
page.on("console", async msg => {
|
|
// If there is a console.error but an error is not thrown, this will ensure the test fails
|
|
if (msg.type() === "error") {
|
|
const errorMsgText = await Promise.all(
|
|
// TODO can we do this without internal properties?
|
|
msg.args().map(arg => arg._remoteObject.description)
|
|
);
|
|
throw new Error(`Console error: ${errorMsgText}`);
|
|
}
|
|
console.log(`PAGE LOG: ${msg.text()}`);
|
|
});
|
|
}
|
|
page.on("pageerror", err => {
|
|
throw new Error(`Console error: ${err}`);
|
|
});
|
|
const utils = puppeteerUtils(page);
|
|
const cxgActions = cellxgeneActions(page);
|
|
return [browser, page, utils, cxgActions];
|
|
}
|