From 240d8d29f33cc27a72c1894750b180304ba1d043 Mon Sep 17 00:00:00 2001 From: cvacalares Date: Sun, 29 Jun 2025 19:46:22 -0700 Subject: [PATCH 1/4] Add function to compare values of a cell in an excel file. Add step definition to compare values of a cell in excel file. Add support for downloading files. --- cucumber.conf.js | 77 ++++++++++++++++++++++++++-- package.json | 7 +-- src/features/BI-2389.feature | 26 ++++++++++ src/features/BreedingMethods.feature | 1 - src/step_definitions/steps.js | 41 ++++++++++++++- 5 files changed, 144 insertions(+), 8 deletions(-) create mode 100644 src/features/BI-2389.feature diff --git a/cucumber.conf.js b/cucumber.conf.js index 3e73eb15..f0aab7dd 100644 --- a/cucumber.conf.js +++ b/cucumber.conf.js @@ -23,6 +23,20 @@ Before(async function ({ pickle }) { ); console.log("tmpUserDataDir:", this.tmpUserDataDir); + // Create a custom download folder for this scenario + this.tmpDownloadDir = fs.mkdtempSync( + path.join(os.tmpdir(), "nw-chrome-downloads-") + ); + console.log("tmpDownloadDir:", this.tmpDownloadDir); + + // Initialize browser.globals.downloadedFilePath + if (!this.browser) { + // browser not initialized yet, will set after launch + this._initDownloadedFilePath = true; + } else { + this.browser.globals.downloadedFilePath = null; + } + const chromeArgs = [ "--no-sandbox", "--disable-dev-shm-usage", @@ -37,9 +51,16 @@ Before(async function ({ pickle }) { "--ignore-certificate-errors", "--allow-insecure-localhost", "--window-size=1920,1080", - "--headless=new", + // "--headless=new", ]; + const chromePrefs = { + "download.default_directory": this.tmpDownloadDir, + "download.prompt_for_download": false, + "download.directory_upgrade": true, + "safebrowsing.enabled": true, + }; + const webdriver = {}; if (this.parameters["webdriver-host"]) webdriver.host = this.parameters["webdriver-host"]; @@ -72,6 +93,7 @@ Before(async function ({ pickle }) { browserName: "chrome", "goog:chromeOptions": { args: chromeArgs, + prefs: chromePrefs, // <-- add prefs for downloads }, }, }); @@ -85,6 +107,12 @@ Before(async function ({ pickle }) { this.browser = await this.client.launchBrowser(); this.browser.globals.timestamp = Date.now(); + + // Set downloadedFilePath on browser.globals after browser is available + if (this._initDownloadedFilePath) { + this.browser.globals.downloadedFilePath = null; + delete this._initDownloadedFilePath; + } }); After(async function (testCase) { @@ -95,12 +123,20 @@ After(async function (testCase) { } if (this.browser) { - await this.browser.quit(); + // await this.browser.quit(); } if (this.tmpUserDataDir) { fs.rmSync(this.tmpUserDataDir, { recursive: true, force: true }); } + if (this.tmpDownloadDir) { + fs.rmSync(this.tmpDownloadDir, { recursive: true, force: true }); + } + + // Clear browser.globals.downloadedFilePath after scenario + if (this.browser && this.browser.globals) { + this.browser.globals.downloadedFilePath = null; + } if (!this.browser?.globals?.run?.browserName) { const caps = this.browser.capabilities; @@ -117,4 +153,39 @@ After(async function (testCase) { console.error("Error saving run metadata:", err); } } -}); \ No newline at end of file +}); + +/** + * Wait for a file to appear in the default download directory and return its path. + * Usage: await getLatestDownloadedFile(10000); + */ +async function getLatestDownloadedFile(timeoutMs = 10000) { + // Resolve the default download directory from Chrome options + let downloadDir = null; + // Try to get from the first browser instance if available + if (global.browser?.options?.desiredCapabilities?.["goog:chromeOptions"]?.prefs?.["download.default_directory"]) { + downloadDir = global.browser.options.desiredCapabilities["goog:chromeOptions"].prefs["download.default_directory"]; + } + // Fallback: try to get from process.env or hardcoded path if needed + if (!downloadDir) { + downloadDir = require("os").tmpdir(); + } + + const fs = require("fs"); + const path = require("path"); + const start = Date.now(); + while (Date.now() - start < timeoutMs) { + const files = fs.readdirSync(downloadDir).filter((f) => !f.endsWith(".crdownload")); + if (files.length > 0) { + // Optionally, sort by mtime to get the latest file + const filePaths = files.map((f) => path.join(downloadDir, f)); + filePaths.sort((a, b) => fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs); + return filePaths[0]; + } + await new Promise((res) => setTimeout(res, 500)); + } + throw new Error("No downloaded file found in time"); +} + +// Export for use in step definitions +module.exports.getLatestDownloadedFile = getLatestDownloadedFile; \ No newline at end of file diff --git a/package.json b/package.json index 678062a8..c730c274 100644 --- a/package.json +++ b/package.json @@ -16,14 +16,15 @@ "license": "ISC", "devDependencies": { "@cucumber/cucumber": "^11.2.0", - "cucumber-html-reporter": "^7.2.0", "@cucumber/pretty-formatter": "*", "@slime/stopwatch": "^1.0.5", + "chromedriver": "^136.0.0", + "cucumber-html-reporter": "^7.2.0", "edgedriver": "^5.6.0", + "exceljs": "^4.4.0", "geckodriver": "^4.4.1", "iedriver": "^3.4.0", "mkdirp": "^1.0.4", - "nightwatch": "^3.12.0", - "chromedriver": "^136.0.0" + "nightwatch": "^3.12.0" } } diff --git a/src/features/BI-2389.feature b/src/features/BI-2389.feature new file mode 100644 index 00000000..871f51ce --- /dev/null +++ b/src/features/BI-2389.feature @@ -0,0 +1,26 @@ +Feature: Verify correct entry numbers on all germplasm export list + + Background: + Given a new program is created + + @BI-2389 + Scenario Outline: The ontology can be case insensitive + Given user logs in as "Cucumber Breeder" + When user selects "*" on program-selection page + And user selects "Germplasm" in top-level navigation + And user selects "Manage Germplasm" button + And user selects "Import file" menu item + And user uploads Germplasm "GermplasmSample.xlsx" file + And user selects 'Import' button + When user sets "GermplasmSort" in List Name field of import page + When user sets "GermplasmSort" in List Description field of import page + And user selects "Confirm" button + And user pause for "5" seconds + And user selects "Germplasm" in top-level navigation + And user selects "Manage Germplasm" button + And user selects "Download file" button + Then the value of column "GID" and column "Entry No" at row 1 in the downloaded file should be equal + Then the value of column "GID" and column "Entry No" at row 2 in the downloaded file should be equal + Then the value of column "GID" and column "Entry No" at row 3 in the downloaded file should be equal + Then the value of column "GID" and column "Entry No" at row 4 in the downloaded file should be equal + \ No newline at end of file diff --git a/src/features/BreedingMethods.feature b/src/features/BreedingMethods.feature index 12c8da7d..0756889b 100644 --- a/src/features/BreedingMethods.feature +++ b/src/features/BreedingMethods.feature @@ -1,7 +1,6 @@ Feature: Breeding Methods @BI-1805 - @debug Scenario Outline: Breeding Methods Management Given user logs in as "sysad" And user selects "System Administration" on program-selection page diff --git a/src/step_definitions/steps.js b/src/step_definitions/steps.js index 5ad920ac..88ea3c91 100644 --- a/src/step_definitions/steps.js +++ b/src/step_definitions/steps.js @@ -1,4 +1,5 @@ const { Given, Then, When, World } = require("@cucumber/cucumber"); +const { getLatestDownloadedFile } = require('../../cucumber.conf.js'); const path = require("path"); const importFolder = path.join(__dirname, "../", "files", "TraitImport"); const germplasmFolder = path.join(__dirname, "../", "files", "GermplasmImport"); @@ -11,6 +12,7 @@ const genotypeSamplesFolder = path.join( const user = {}; const helpers = require("./helpers.js"); const assert = require("assert"); +const ExcelJS = require("exceljs"); Given(/^user logs with valid credentials$/, async function () { await this.browser.page.page().navigate(); @@ -1367,6 +1369,33 @@ Then("user can not see {string} button", async function (args0) { }); }); +/** + * Compare the value of a source column and target column at a specific row in an Excel file. + * @param {string} filePath - Path to the Excel file. + * @param {string} sourceCol - Source column letter (e.g., "A"). + * @param {string} targetCol - Target column letter (e.g., "B"). + * @param {number} rowNum - Row number (1-based). + * @returns {Promise} - true if equal, false otherwise. + */ +async function compareCellValuesByColumns(filePath, sourceCol, targetCol, rowNum) { + const workbook = new ExcelJS.Workbook(); + await workbook.xlsx.readFile(filePath); + const worksheet = workbook.worksheets[0]; + const sourceValue = worksheet.getCell(`${sourceCol}${rowNum}`).value; + const targetValue = worksheet.getCell(`${targetCol}${rowNum}`).value; + return sourceValue === targetValue; +} + +// Example Cucumber step +Then( + /^the value of column "([^"]*)" and column "([^"]*)" at row (\d+) in the downloaded file should be equal$/, + async function (sourceCol, targetCol, rowNum) { + const filePath = this.browser.globals.downloadedFilePath; + const isEqual = await compareCellValuesByColumns(filePath, sourceCol, targetCol, Number(rowNum)); + assert.ok(isEqual, `Column ${sourceCol}${rowNum} and ${targetCol}${rowNum} values are not equal`); + } +); + //functions async function setUserName(name) { user.userName = name; @@ -1600,10 +1629,20 @@ async function selectsImportButton() { } async function selectsButton(args1) { + //download file + if (args1 === "Download file") { + this.browser.page.page().click('#germplasm-download-file'); // Replace with your actual selector + const filePath = await getLatestDownloadedFile(); + this.browser.globals.downloadedFilePath = filePath; + console.log("File downloaded to: " + filePath); + return; + } + const selectorWithModal = { - selector: `//*[@class='modal is-active']//button[normalize-space(.)='${args1}']`, + selector: `//*[@class='modal is-active']//button[normalize-space()='${args1}']`, locateStrategy: "xpath", }; + try { await this.browser.page .page() From ec50d7d11491b2ce1691977cf0702c18de4295e4 Mon Sep 17 00:00:00 2001 From: cvacalares Date: Sun, 29 Jun 2025 19:48:52 -0700 Subject: [PATCH 2/4] Remove commented out code. --- cucumber.conf.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber.conf.js b/cucumber.conf.js index f0aab7dd..b15903b1 100644 --- a/cucumber.conf.js +++ b/cucumber.conf.js @@ -51,7 +51,7 @@ Before(async function ({ pickle }) { "--ignore-certificate-errors", "--allow-insecure-localhost", "--window-size=1920,1080", - // "--headless=new", + "--headless=new", ]; const chromePrefs = { @@ -123,7 +123,7 @@ After(async function (testCase) { } if (this.browser) { - // await this.browser.quit(); + await this.browser.quit(); } if (this.tmpUserDataDir) { From b848dc2b5dbfbdd90539bd7b23ce944634203270 Mon Sep 17 00:00:00 2001 From: cvacalares Date: Sun, 29 Jun 2025 23:11:51 -0700 Subject: [PATCH 3/4] Update a directory to be used. --- cucumber.conf.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber.conf.js b/cucumber.conf.js index b15903b1..ae78a5fc 100644 --- a/cucumber.conf.js +++ b/cucumber.conf.js @@ -52,6 +52,7 @@ Before(async function ({ pickle }) { "--allow-insecure-localhost", "--window-size=1920,1080", "--headless=new", + `--user-data-dir=${this.tmpUserDataDir}`, // <-- set as argument ]; const chromePrefs = { @@ -93,7 +94,7 @@ Before(async function ({ pickle }) { browserName: "chrome", "goog:chromeOptions": { args: chromeArgs, - prefs: chromePrefs, // <-- add prefs for downloads + prefs: chromePrefs, }, }, }); From 76c77d806e5ac09aba09b8d470ebb6e154a57f94 Mon Sep 17 00:00:00 2001 From: cvacalares Date: Mon, 11 Aug 2025 18:01:22 -0700 Subject: [PATCH 4/4] Remove unwanted comments. --- src/step_definitions/steps.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/step_definitions/steps.js b/src/step_definitions/steps.js index 88ea3c91..6e2beae7 100644 --- a/src/step_definitions/steps.js +++ b/src/step_definitions/steps.js @@ -1386,7 +1386,6 @@ async function compareCellValuesByColumns(filePath, sourceCol, targetCol, rowNum return sourceValue === targetValue; } -// Example Cucumber step Then( /^the value of column "([^"]*)" and column "([^"]*)" at row (\d+) in the downloaded file should be equal$/, async function (sourceCol, targetCol, rowNum) { @@ -1631,7 +1630,7 @@ async function selectsImportButton() { async function selectsButton(args1) { //download file if (args1 === "Download file") { - this.browser.page.page().click('#germplasm-download-file'); // Replace with your actual selector + this.browser.page.page().click('#germplasm-download-file'); const filePath = await getLatestDownloadedFile(); this.browser.globals.downloadedFilePath = filePath; console.log("File downloaded to: " + filePath);