ci: Fix golden app tests and more (#15172)

* - Save the DOM on test failures.
- Handle promise rejection in tests.
- Save console logs to a file instead of stdout

* - Fix golden app tests
- Upgrade widgets in golden app to latest version

* - Removing logging to file temporarily
- Add test index to the file names

* Some final cleanup

Co-authored-by: Satish Gandham <satish@appsmith.com>
This commit is contained in:
Satish Gandham 2022-07-15 14:40:45 +05:30 committed by GitHub
parent 458715f0ac
commit c9a7587dcf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 17467 additions and 135 deletions

33
app/client/perf/readme.md Normal file
View File

@ -0,0 +1,33 @@
### Adding credentials to app export
- In the exported app add this property under `datasourceList` in the item corresponding to the plugin you are adding credentials for.
```
"datasourceConfiguration": {
"connection": {
"mode": "READ_WRITE",
"ssl": {
"authType": "DEFAULT"
}
},
"endpoints": [{
"host": "localhost",
"port": 5432
}],
"sshProxyEnabled": false
},
```
- Add this key at the top level
```
"decryptedFields": {
"PostgresGolden": {
"password": "********",
"authType": "com.appsmith.external.models.DBAuth",
"dbAuth": {
"authenticationType": "dbAuth",
"authenticationType": "dbAuth",
"username": "********",
"databaseName": "db_name"
}
}
},
```

View File

@ -18,7 +18,7 @@ const metricsToLog = [
"LongTask", "LongTask",
]; ];
const supabaseKey = process.env.APPSMITH_PERF_SUPABASE_SECRET; const supabaseKey = process.env.APPSMITH_PERF_SUPABASE_SECRET || "empty";
const supabase = createClient(supabaseUrl, supabaseKey); const supabase = createClient(supabaseUrl, supabaseKey);
const actionRows = Object.keys(actions).map((action) => ({ const actionRows = Object.keys(actions).map((action) => ({

View File

@ -15,6 +15,7 @@ if (!fs.existsSync(dir)) {
glob("./tests/*.perf.js", {}, async function(er, files) { glob("./tests/*.perf.js", {}, async function(er, files) {
// Initial setup // Initial setup
await cp.execSync(`node ./tests/initial-setup.js`, { stdio: "inherit" }); await cp.execSync(`node ./tests/initial-setup.js`, { stdio: "inherit" });
files.forEach(async (file) => { files.forEach(async (file) => {
await cp.execSync(`node ${file}`, { stdio: "inherit" }); // Logging to terminal, log it to a file in future? await cp.execSync(`node ${file}`, { stdio: "inherit" }); // Logging to terminal, log it to a file in future?
}); });

View File

@ -26,6 +26,7 @@ const selectors = {
module.exports = class Perf { module.exports = class Perf {
constructor(launchOptions = {}) { constructor(launchOptions = {}) {
this.iteration = launchOptions.iteration || 0; // Current iteration number
this.launchOptions = { this.launchOptions = {
defaultViewport: null, defaultViewport: null,
args: ["--window-size=1920,1080"], args: ["--window-size=1920,1080"],
@ -50,26 +51,48 @@ module.exports = class Perf {
.pop() .pop()
.replace(".perf.js", ""); .replace(".perf.js", "");
global.APP_ROOT = path.join(__dirname, ".."); //Going back one level from src folder to /perf global.APP_ROOT = path.join(__dirname, ".."); //Going back one level from src folder to /perf
process.on("unhandledRejection", async (reason, p) => {
console.error("Unhandled Rejection at: Promise", p, "reason:", reason); process.on("unhandledRejection", this.handleRejections);
const fileName = sanitize(
`${this.currentTestFile}__${this.currentTrace}`,
);
const screenshotPath = `${APP_ROOT}/traces/reports/${fileName}-${getFormattedTime()}.png`;
await this.page.screenshot({
path: screenshotPath,
});
if (this.currentTrace) {
await this.stopTrace();
}
this.browser.close();
});
} }
handleRejections = async (reason = "", p = "") => {
console.error("Unhandled Rejection at: Promise", p, "reason:", reason);
const fileName = sanitize(`${this.currentTestFile}__${this.currentTrace}`);
if (!this.page) {
console.warn("No page instance was found", this.currentTestFile);
return;
}
const screenshotPath = `${APP_ROOT}/traces/reports/${fileName}-${getFormattedTime()}.png`;
await this.page.screenshot({
path: screenshotPath,
});
const pageContent = await this.page.evaluate(() => {
return document.querySelector("body").innerHTML;
});
fs.writeFile(
`${APP_ROOT}/traces/reports/${fileName}-${getFormattedTime()}.html`,
pageContent,
(err) => {
if (err) {
console.error(err);
}
},
);
if (this.currentTrace) {
await this.stopTrace();
}
this.browser.close();
};
/** /**
* Launches the browser and, gives you the page * Launches the browser and, gives you the page
*/ */
launch = async () => { launch = async () => {
await cleanTheHost(); await cleanTheHost();
await delay(3000);
this.browser = await puppeteer.launch(this.launchOptions); this.browser = await puppeteer.launch(this.launchOptions);
const pages_ = await this.browser.pages(); const pages_ = await this.browser.pages();
this.page = pages_[0]; this.page = pages_[0];
@ -97,7 +120,9 @@ module.exports = class Perf {
await this.page._client.send("HeapProfiler.collectGarbage"); await this.page._client.send("HeapProfiler.collectGarbage");
await delay(1000, `After clearing memory`); await delay(1000, `After clearing memory`);
const path = `${APP_ROOT}/traces/${action}-${getFormattedTime()}-chrome-profile.json`; const path = `${APP_ROOT}/traces/${action}-${
this.iteration
}-${getFormattedTime()}-chrome-profile.json`;
await this.page.tracing.start({ await this.page.tracing.start({
path: path, path: path,

File diff suppressed because one or more lines are too long

View File

@ -6,19 +6,19 @@ const { delay, makeid } = require("../src/utils/utils");
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
const SEL = { const SEL = {
category: "div[label=Uncategorized]", category: "div.rc-select-item[title=Uncategorized]",
multiSelect: ".rc-select", multiSelect: ".rc-select",
table: "#tablejabdu9f16g", table: "#tablejabdu9f16g",
tableData: ".t--property-control-tabledata textarea", tableData: ".t--property-control-tabledata textarea",
tableRow: tableRow:
"#tablejabdu9f16g > div.tableWrap > div > div:nth-child(1) > div > div.tbody.no-scroll > div:nth-child(6) > div:nth-child(2)", "#tablejabdu9f16g > div.tableWrap > div > div:nth-child(1) > div > div.tbody.no-scroll > div:nth-child(6) > div:nth-child(2)",
titleInput: ".appsmith_widget_in8e51pg3y input", titleInput: ".appsmith_widget_armli8hauj input",
updateButton: updateButton:
"#comment-overlay-wrapper-4gnygu5jew > div > div > div > div > button", "#comment-overlay-wrapper-4gnygu5jew > div > div > div > div > button",
tableRowCell: tableRowCell:
"#tablejabdu9f16g > div.tableWrap > div > div:nth-child(1) > div > div.tbody.no-scroll > div:nth-child(6) > div:nth-child(2) > div > span > span > span", "#tablejabdu9f16g > div.tableWrap > div > div:nth-child(1) > div > div.tbody.no-scroll > div:nth-child(6) > div:nth-child(2) > div > span > span > span",
deletePostButton: deletePostButton:
"#tablejabdu9f16g > div.tableWrap > div > div:nth-child(1) > div > div.tbody.no-scroll > div:nth-child(1) > div:nth-child(27) > div > div > button", "#tablejabdu9f16g > div.tableWrap > div > div:nth-child(1) > div > div.tbody.no-scroll > div:nth-child(1) > div:last-child > div > div > button",
modalTitle: "#reyoxo4oec", modalTitle: "#reyoxo4oec",
closeModal: closeModal:
"#comment-overlay-wrapper-lryg8kw537 > div > div > div > div > button", "#comment-overlay-wrapper-lryg8kw537 > div > div > div > div > button",
@ -26,94 +26,93 @@ const SEL = {
commentsTableTitle: "#urzv99hdc8", commentsTableTitle: "#urzv99hdc8",
}; };
async function testTyping() { async function testGoldenApp(iteration) {
const perf = new Perf(); const perf = new Perf({ iteration });
await perf.launch(); try {
const page = perf.getPage(); await perf.launch();
const page = perf.getPage();
await perf.importApplication( await perf.importApplication(
`${APP_ROOT}/tests/dsl/blog-admin-app-postgres.json`, `${APP_ROOT}/tests/dsl/blog-admin-app-postgres.json`,
); );
await delay(5000, "for newly created page to settle down"); await delay(5000, "for newly created page to settle down");
// Make the elements of the dropdown render // Make the elements of the dropdown render
await page.waitForSelector(SEL.multiSelect); await page.waitForSelector(SEL.multiSelect);
await page.click(SEL.multiSelect); await page.click(SEL.multiSelect);
await perf.startTrace(actions.SELECT_CATEGORY); await perf.startTrace(actions.SELECT_CATEGORY);
await page.waitForSelector(SEL.category); await page.waitForSelector(SEL.category);
await page.click(SEL.category); await page.click(SEL.category);
await perf.stopTrace(); await perf.stopTrace();
// Focus on the table widget // Focus on the table widget
await page.waitForSelector(SEL.table); await page.waitForSelector(SEL.table);
await page.click(SEL.table);
// Profile table Data binding // Not sure why it needs two clicks to focus
await perf.startTrace(actions.BIND_TABLE_DATA); await page.click(SEL.table);
await page.waitForSelector(SEL.tableData); await page.click(SEL.table);
await page.type(SEL.tableData, "{{SelectQuery.data}}");
await page.waitForSelector(SEL.tableRow);
await perf.stopTrace();
// Click on table row // Profile table Data binding
await perf.startTrace(actions.CLICK_ON_TABLE_ROW); await perf.startTrace(actions.BIND_TABLE_DATA);
await page.click(SEL.tableRow); await page.waitForSelector(SEL.tableData);
await page.waitForFunction( await page.type(SEL.tableData, "{{SelectQuery.data}}");
`document.querySelector("${SEL.titleInput}").value.includes("Template: Comments")`, await page.waitForSelector(SEL.tableRow);
); await perf.stopTrace();
await perf.stopTrace(); // Click on table row
await perf.startTrace(actions.CLICK_ON_TABLE_ROW);
await page.click(SEL.tableRow);
await page.waitForFunction(
`document.querySelector("${SEL.titleInput}").value.includes("Template: Comments")`,
);
// Edit title await perf.stopTrace();
await page.waitForSelector(SEL.titleInput);
await perf.startTrace(actions.UPDATE_POST_TITLE);
const randomString = makeid(); // Edit title
await page.type(SEL.titleInput, randomString); await page.waitForSelector(SEL.titleInput);
await delay(5000, "For the evaluations to comeback?"); await perf.startTrace(actions.UPDATE_POST_TITLE);
await page.waitForSelector(SEL.updateButton); const randomString = makeid();
await page.click(SEL.updateButton); await page.type(SEL.titleInput, randomString);
// When the row is updated, selected row changes. await delay(5000, "For the evaluations to comeback?");
// await page.waitForSelector(SEL.tableRowCell);
await page.waitForFunction(
`document.querySelector("${SEL.table}").textContent.includes("${randomString}")`,
);
await perf.stopTrace();
// Open modal await page.waitForSelector(SEL.updateButton);
await page.waitForSelector(SEL.deletePostButton); await page.click(SEL.updateButton);
await perf.startTrace(actions.OPEN_MODAL); // When the row is updated, selected row changes.
await page.click(SEL.deletePostButton); // await page.waitForSelector(SEL.tableRowCell);
await page.waitForSelector(SEL.modalTitle); await page.waitForFunction(
await perf.stopTrace(); `document.querySelector("${SEL.table}").textContent.includes("${randomString}")`,
);
await perf.stopTrace();
// Close modal // Open modal
await page.waitForSelector(SEL.closeModal); await page.waitForSelector(SEL.deletePostButton);
await perf.startTrace(actions.CLOSE_MODAL); await perf.startTrace(actions.OPEN_MODAL);
await page.click(SEL.closeModal); await page.click(SEL.deletePostButton);
await delay(3000, "wait after closing modal"); await page.waitForSelector(SEL.modalTitle);
await perf.stopTrace(); await perf.stopTrace();
/* Enable this after the new entity explorer // Close modal
// Navigate to a page await page.waitForSelector(SEL.closeModal);
await page.waitForSelector(SEL.commentsPageLink); await perf.startTrace(actions.CLOSE_MODAL);
await perf.startTrace("Switch page"); await page.click(SEL.closeModal);
await page.click(SEL.commentsPageLink); await delay(3000, "wait after closing modal");
await page.waitForSelector(SEL.commentsTableTitle); await perf.stopTrace();
await perf.stopTrace();
*/ await perf.generateReport();
await perf.generateReport(); await perf.close();
await perf.close(); } catch (e) {
await perf.handleRejections(e);
await perf.close();
}
} }
async function runTests() { async function runTests() {
await testTyping(); for (let i = 0; i < 5; i++) {
await testTyping(); await testGoldenApp(i + 1);
await testTyping(); }
await testTyping();
await testTyping();
} }
runTests(); runTests();

View File

@ -4,38 +4,43 @@ const dsl = require("./dsl/simple-typing").dsl;
process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0; process.env["NODE_TLS_REJECT_UNAUTHORIZED"] = 0;
async function testTyping() { async function sampleTest(iteration) {
const perf = new Perf(); const perf = new Perf({ iteration });
await perf.launch(); try {
const page = perf.getPage(); await perf.launch();
await perf.loadDSL(dsl);
const selector = "input.bp3-input"; // Input selector const page = perf.getPage();
await page.waitForSelector(selector); await perf.loadDSL(dsl);
const input = await page.$(selector);
await perf.startTrace("Edit input"); const selector = "input.bp3-input"; // Input selector
await page.type(selector, "Hello Appsmith"); await page.waitForSelector(selector);
await perf.stopTrace(); const input = await page.$(selector);
await perf.startTrace("Clear input"); await perf.startTrace("Edit input");
await input.click({ clickCount: 3 }); await page.type(selector, "Hello Appsmith");
await input.press("Backspace"); await perf.stopTrace();
await perf.stopTrace();
await perf.startTrace("Edit input again"); await perf.startTrace("Clear input");
await page.type(selector, "Howdy satish"); await input.click({ clickCount: 3 });
await perf.stopTrace(); await input.press("Backspace");
await perf.stopTrace();
await perf.generateReport(); await perf.startTrace("Edit input again");
await perf.close(); await page.type(selector, "Howdy satish");
await perf.stopTrace();
await perf.generateReport();
await perf.close();
} catch (e) {
await perf.handleRejections(e);
await perf.close();
}
} }
async function runTests() { async function runTests() {
await testTyping(); for (let i = 0; i < 5; i++) {
await testTyping(); await sampleTest(i + 1);
await testTyping(); }
await testTyping();
await testTyping();
} }
runTests(); runTests();

View File

@ -10,9 +10,9 @@ const SEL = {
first_option_item: ".menu-item-text:nth-child(1)", first_option_item: ".menu-item-text:nth-child(1)",
}; };
async function testSelectOptionsRender() { async function testSelectOptionsRender(iteration) {
const perf = new Perf({ iteration });
try { try {
const perf = new Perf();
await perf.launch(); await perf.launch();
const page = perf.getPage(); const page = perf.getPage();
@ -34,15 +34,15 @@ async function testSelectOptionsRender() {
await perf.generateReport(); await perf.generateReport();
await perf.close(); await perf.close();
} catch (e) { } catch (e) {
console.log(e); await perf.handleRejections(e);
await perf.close();
} }
} }
async function runTests() { async function runTests() {
await testSelectOptionsRender(); for (let i = 0; i < 5; i++) {
await testSelectOptionsRender(); await testSelectOptionsRender(i + 1);
await testSelectOptionsRender(); }
await testSelectOptionsRender();
await testSelectOptionsRender();
} }
runTests(); runTests();