diff --git a/.gitignore b/.gitignore index b3987c2c..3dcc95dd 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,7 @@ npm-debug.log* !.env.example +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/package-lock.json b/package-lock.json index 78d5adbb..93f66f22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,6 +38,8 @@ }, "devDependencies": { "@capacitor/cli": "^2.4.7", + "@playwright/test": "^1.44.1", + "@types/node": "^20.14.2", "@types/papaparse": "5.3.1", "@typescript-eslint/eslint-plugin": "~5.26.0", "@typescript-eslint/parser": "~5.26.0", @@ -3272,6 +3274,21 @@ "node": ">=14" } }, + "node_modules/@playwright/test": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.44.1.tgz", + "integrity": "sha512-1hZ4TNvD5z9VuhNJ/walIjvMVvYkZKf71axoF/uiAqpntQJXpG64dlXhoDXE3OczPuTuvjf/M5KWFg5VAVUS3Q==", + "dev": true, + "dependencies": { + "playwright": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", @@ -3723,9 +3740,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.27", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.27.tgz", - "integrity": "sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==", + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dependencies": { "undici-types": "~5.26.4" } @@ -13145,6 +13162,50 @@ "node": ">=8" } }, + "node_modules/playwright": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.44.1.tgz", + "integrity": "sha512-qr/0UJ5CFAtloI3avF95Y0L1xQo6r3LQArLIg/z/PoGJ6xa+EwzrwO5lpNr/09STxdHuUoP2mvuELJS+hLdtgg==", + "dev": true, + "dependencies": { + "playwright-core": "1.44.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plist": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz", diff --git a/package.json b/package.json index 525bd092..577c9227 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,8 @@ }, "devDependencies": { "@capacitor/cli": "^2.4.7", + "@playwright/test": "^1.44.1", + "@types/node": "^20.14.2", "@types/papaparse": "5.3.1", "@typescript-eslint/eslint-plugin": "~5.26.0", "@typescript-eslint/parser": "~5.26.0", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 00000000..a1bdade4 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,67 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// require('dotenv').config(); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + // baseURL: 'http://127.0.0.1:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + } + + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, + + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], + + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +}); diff --git a/tests/fulfillment.spec.js b/tests/fulfillment.spec.js new file mode 100644 index 00000000..4f81288e --- /dev/null +++ b/tests/fulfillment.spec.js @@ -0,0 +1,223 @@ +const { test, expect, chromium} = require('@playwright/test'); +const fs = require('fs'); + +// Create a writable stream for logging +const logFile = fs.createWriteStream('playwright-logs.txt', { flags: 'a' }); +const errorFile = fs.createWriteStream('playwright-errors.txt', { flags: 'a' }); +const networkLogFile = fs.createWriteStream('network-logs.txt', { flags: 'a' }); +const consoleLogFile = fs.createWriteStream('console-logs.txt', { flags: 'a' }); + +// Redirect console.log and console.error to the log files +const originalLog = console.log; +const originalError = console.error; + +console.log = (...args) => { + logFile.write(args.join(' ') + '\n'); + originalLog.apply(console, args); +}; + +console.error = (...args) => { + errorFile.write(args.join(' ') + '\n'); + originalError.apply(console, args); +}; + +test ('Fulfillment', async () => { + test.setTimeout(8000000); + + //Set up the environment + const browser = await chromium.launch(); + const context = await browser.newContext({headless: false, viewport: { width: 1380, height: 700 }}); + await context.tracing.start({ screenshots: true, snapshots: true }); // launch the browser in a visible window and set the visible portion of the web page within the browser window. + const page = await context.newPage(); + + page.on('request', request => { + networkLogFile.write(`Request: ${request.method()} ${request.url()}\n`); + if (request.postData()) { + networkLogFile.write(`Post Data: ${request.postData()}\n`); + } + }); + + page.on('response', response => { + networkLogFile.write(`Response: ${response.status()} ${response.url()}\n`); + response.text().then(text => { + networkLogFile.write(`Response Body: ${text}\n`); + }).catch(error => { + networkLogFile.write(`Error reading response body: ${error}\n`); + }); + }); + + page.on('console', msg => { + consoleLogFile.write(`Console Log: ${msg.type()} ${msg.text()}\n`); + for (let i = 0; i < msg.args().length; ++i) + consoleLogFile.write(`Console Log Arg[${i}]: ${msg.args()[i]}\n`); + }); + // try-catch block for error handling + try { + + // Go to URL and verify the heading + await page.goto("https://launchpad.hotwax.io"); + await page.waitForTimeout(2000); + const loginButton = '.ion-color.ion-color-danger.md.button.button-outline.ion-activatable.ion-focusable'; + if ((await page.isVisible(loginButton)) && (await page.isEnabled(loginButton))) await page.click(loginButton); + await page.waitForTimeout(2000); + await page.waitForSelector('.flex', { visible: true}); + await page.fill('input[name="instanceUrl"]', 'demo-oms'); + const instanceEnteredValue = await page.$eval('input[name="instanceUrl"]', instance => instance.value); + await page.waitForTimeout(2000); + await page.keyboard.press('Enter'); + await page.waitForTimeout(2000); + await page.fill('input[name="username"]','hotwax.user'); + const usernameEnteredValue = await page.$eval('input[name="username"]', username => username.value); + await page.locator('input[name="password"]').fill('hotwax@786'); + await page.waitForTimeout(2000); + await page.keyboard.press('Enter'); + await page.waitForTimeout(2000); + await page.waitForSelector('#app > ion-app > ion-router-outlet > div:nth-child(1) > ion-content > main', { visible: true}); + const appCard = await page.locator('ion-card:has-text("Fulfillment")'); + await appCard.locator('.ion-color.ion-color-medium.md.button.button-clear.in-buttons.button-has-icon-only.ion-activatable.ion-focusable').nth(1).click(); + await page.waitForSelector('.split-pane-side.md.menu-type-overlay.menu-side-start.menu-pane-visible.menu-enabled', { visible: true}); + await page.locator('ion-label:has-text("Settings")').click(); + await page.waitForSelector('.user-profile'); + + const selectors = { + usernameElement: 'div.user-profile ion-card-subtitle', + instanceElement: '#main-content > div > ion-content > section:nth-child(3) > ion-card:nth-child(1) > ion-card-header > ion-card-title', + appVersionElement: '.section-header div p.overline:nth-child(2)', + buildDateTimeElement: '.section-header div p.overline:nth-child(1)' + }; + const username = await page.locator(selectors.usernameElement).evaluate(element => element.textContent.trim()); + const instance = await page.locator(selectors.instanceElement).evaluate(element => element.textContent.trim()); + const appVersion = await page.locator(selectors.appVersionElement).evaluate(element => element.textContent.trim()); + const buildDateTime= await page.locator(selectors.buildDateTimeElement).evaluate(element => element.textContent.trim()); + if (instance && username && appVersion && buildDateTime) { + await expect(username).toContain(usernameEnteredValue); + await expect(instance).toContain(instanceEnteredValue); + } + await page.locator('#main-content > div > ion-content > section:nth-child(3) > ion-card:nth-child(3) > ion-item').click(); + await page.waitForSelector('.popover-viewport', { visible: true}); + let dropdownItems = await page.$$('.select-interface-option.md.sc-ion-select-popover-md.item.item-lines-default.item-fill-none.item-has-interactive-control.ion-activatable.ion-focusable'); + console.log(dropdownItems.length, 'facilities found'); + for (let i = 0; i < dropdownItems.length; i++) { + await dropdownItems[i].evaluate(element => element.scrollIntoView()); + await page.waitForTimeout(2000); + await dropdownItems[i].click(); + await page.waitForTimeout(2000); + await page.locator('ion-label:has-text("Open")').click(); + await page.waitForTimeout(3000); + if (await page.isVisible('.md.chip-outline.ion-activatable')){ + await page.waitForTimeout(2000); + let orderCards = await page.$$('.md.chip-outline.ion-activatable'); + const orderCount = await page.locator('#main-content > div > ion-header > ion-toolbar > ion-title'); + const numberOfOrders = await orderCount.evaluate(element => { + const getOrderValue = element.childNodes[0].textContent; + const getOrderValue1 = getOrderValue.split(' ')[0]; + return getOrderValue1;}); + await expect (orderCards.length).toBe(Number(numberOfOrders)); + console.log(numberOfOrders, 'orders are visible'); + await page.locator('ion-buttons.buttons-last-slot.sc-ion-buttons-md-h.sc-ion-buttons-md-s.md ion-menu-button').click(); + await page.waitForSelector('div.ion-page div.menu-inner'); + const matchOrderNumber = await page.$eval('.radio-checked', element => element.textContent); + //const matchOrderNumber1 = matchOrderNumber.split(' ')[0]; + await expect (matchOrderNumber).toContain(numberOfOrders.toString()); + await page.waitForTimeout(2000); + const orderFilterOptions = await page.$$('.md.in-item.radio-justify-start.radio-alignment-center.radio-label-placement-end'); + let randomIndex = Math.floor(Math.random() * orderFilterOptions.length); + const randomOption = orderFilterOptions[randomIndex]; + const filterOptionText = await randomOption.textContent(); + await randomOption.click(); + if (await page.isVisible('.ion-color.ion-color-light.md.item')) await page.click('.ion-color.ion-color-light.md.item'); + orderCards = await page.$$('.md.chip-outline.ion-activatable'); + await expect (filterOptionText).toContain(orderCards.length.toString()); + await page.waitForTimeout(2000); + + while (await page.isVisible('.md.chip-outline.ion-activatable')){ + await page.locator('.md.chip-outline.ion-activatable').nth(0).click(); + await page.waitForTimeout(2000); + await page.locator('ion-item:has-text("Pick order")').click(); + await page.waitForTimeout(4000); + await page.locator('ion-item:has-text("Copy ID")').click(); + await page.waitForTimeout(4000); + const pickerList = await page.$$('.item.md.item-lines-default.item-fill-none.item-has-interactive-control.in-list.ion-activatable.ion-focusable.item-label'); + randomIndex = Math.floor(Math.random() * pickerList.length); + const randomPicker = pickerList[randomIndex]; + await randomPicker.click(); + await page.waitForTimeout(2000); + await page.waitForSelector('ion-modal'); + const ionFabButton = await page.$('ion-modal > div > ion-fab > ion-fab-button'); + if (ionFabButton) { + await ionFabButton.click(); + } else { + console.log('Picklist not saved'); + } + let newTabPopup = page.waitForEvent('popup'); + let newTab = await newTabPopup; + await newTab.waitForLoadState(); + await page.waitForTimeout(4000); + await newTab.close(); + await page.waitForTimeout(2000); + await page.locator('ion-label:has-text("In Progress")').click(); + await page.waitForTimeout(2000); + await page.locator('.searchbar-input.sc-ion-searchbar-md').click(); + await page.keyboard.press('Control+V'); + await page.keyboard.press('Enter'); + await page.waitForTimeout(2000); + await page.locator('.md.button.button-solid.ion-activatable.ion-focusable').nth(0).click(); + await page.waitForSelector('.sc-ion-alert-md-h.sc-ion-alert-md-s.md'); + await page.waitForTimeout(2000); + const packOptions = await page.locator('.alert-checkbox-label.sc-ion-alert-md'); + const count = await packOptions.count(); + for (let j = 0; j < count; j++) { + await packOptions.nth(j).click(); + await page.waitForTimeout(2000); + } + await page.waitForTimeout(2000); + await page.locator('.alert-button.ion-focusable.ion-activatable.alert-button-role-confirm.sc-ion-alert-md').click(); + newTabPopup = page.waitForEvent('popup'); + newTab = await newTabPopup; + await newTab.waitForLoadState(); + await page.waitForTimeout(4000); + await newTab.close(); + await page.waitForTimeout(2000); + await page.locator('ion-label:has-text("Completed")').click(); + await page.waitForTimeout(3000); + await page.locator('.searchbar-input.sc-ion-searchbar-md').click(); + await page.keyboard.press('Control+V'); + await page.keyboard.press('Enter'); + await page.waitForTimeout(3000); + await page.locator('.md.button.button-solid.ion-activatable.ion-focusable').nth(0).click(); + await page.waitForTimeout(2000); + await page.locator('ion-label:has-text("Open")').click(); + await page.waitForTimeout(2000); + } + } + if (i < dropdownItems.length-1) { + console.log('change facility'); + await page.locator('ion-label:has-text("Settings")').click(); + await page.waitForTimeout(2000); + await page.locator('#main-content > div > ion-content > section:nth-child(3) > ion-card:nth-child(3) > ion-item').click(); + await page.waitForTimeout(2000); + dropdownItems = await page.$$('.select-interface-option.md.sc-ion-select-popover-md.item.item-lines-default.item-fill-none.item-has-interactive-control.ion-activatable.ion-focusable'); + } + } + await page.waitForTimeout(5000); + console.log(appVersion); + console.log(buildDateTime); + // await page.click(".button-solid.ion-activatable.ion-focusable"); + await page.waitForTimeout(3000); + } + catch (error) { + // Log the error for debugging + console.error('Error:', error); + } + finally { + await context.tracing.stop({ path: 'trace.zip' }); + await page.close(); + await browser.close(); + logFile.end(); + errorFile.end(); + networkLogFile.end(); + consoleLogFile.end(); + } +}); + + \ No newline at end of file