Web Dev Solutions

Catalin Mititiuc

From d826a01ff91301d88762a59881ac53053926b5be Mon Sep 17 00:00:00 2001 From: Catalin Mititiuc Date: Sun, 2 Jun 2024 14:46:27 -0700 Subject: Add integration test-helpers and refactor integration tests --- jest.config.integ.cjs | 5 +- package.json | 2 +- public/index.html | 4 +- test/integration/fixtures/scenario-test.svg | 30 ----- test/integration/fixtures/scenario.svg | 24 ++++ test/integration/helpers.cjs | 72 ++++++++++++ test/integration/select.test.js | 172 +++++++++++++--------------- 7 files changed, 179 insertions(+), 130 deletions(-) delete mode 100644 test/integration/fixtures/scenario-test.svg create mode 100644 test/integration/fixtures/scenario.svg diff --git a/jest.config.integ.cjs b/jest.config.integ.cjs index 28562ea..083cbd7 100644 --- a/jest.config.integ.cjs +++ b/jest.config.integ.cjs @@ -5,8 +5,9 @@ module.exports = { globalTeardown: './test/integration/teardown.cjs', setupFiles: ['./test/integration/helpers.cjs'], testPathIgnorePatterns: ['/node_modules/', 'test/unit'], - testTimeout: 5000, + verbose: true, + randomize: true, globals: { testServerUrl: 'http://localhost:3005/' - } + }, }; diff --git a/package.json b/package.json index 620fc02..9c26357 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "scripts": { "start": "node server.cjs", "start:esbuildserver": "node esbuild-server.mjs", - "test:integ": "node --trace-warnings ./node_modules/.bin/jest --detectOpenHandles --config jest.config.integ.cjs", + "test:integ": "node ./node_modules/.bin/jest --detectOpenHandles --config jest.config.integ.cjs", "test:integ:debug": "NODE_INSPECT_RESUME_ON_START=1 node inspect ./node_modules/jest/bin/jest.js --config jest.config.integ.cjs --runInBand", "test": "jest" } diff --git a/public/index.html b/public/index.html index df3fc65..b78df5b 100644 --- a/public/index.html +++ b/public/index.html @@ -145,7 +145,7 @@ Loading... - +
Distance: - @@ -246,7 +246,7 @@ diff --git a/test/integration/fixtures/scenario-test.svg b/test/integration/fixtures/scenario-test.svg deleted file mode 100644 index 77afd6b..0000000 --- a/test/integration/fixtures/scenario-test.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/test/integration/fixtures/scenario.svg b/test/integration/fixtures/scenario.svg new file mode 100644 index 0000000..e5b7121 --- /dev/null +++ b/test/integration/fixtures/scenario.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/integration/helpers.cjs b/test/integration/helpers.cjs index 7e3e0fd..d38f1b6 100644 --- a/test/integration/helpers.cjs +++ b/test/integration/helpers.cjs @@ -1,5 +1,8 @@ const { mkdir, writeFile } = require('node:fs/promises') , path = require('path') + , { By, until } = require('selenium-webdriver') + , { JSDOM } = require('jsdom') + , { HttpResponse } = require('selenium-webdriver/devtools/networkinterceptor') ; global.takeScreenshot = async (driver) => { @@ -14,3 +17,72 @@ global.url = (relativeOrAbsolute) => { const location = new URL(relativeOrAbsolute, global.testServerUrl); return location.href; } + +global.page = { + waitUntilMapLoaded: async function (driver) { + const mapPlaceholderEl = await driver.findElement(By.css('.map-placeholder')); + await driver.wait(until.elementIsNotVisible(mapPlaceholderEl), 1000); + }, + + findElInFrame: async function (frame, locator) { + const driver = frame.getDriver(); + await driver.switchTo().frame(frame); + return driver.findElement(locator); + }, + + expectSelected: async function (el) { + expect(await el.getAttribute('class')).toEqual(expect.stringContaining('selected')); + }, + + expectNotSelected: async function (el) { + expect(await el.getAttribute('class')).toEqual(expect.not.stringContaining('selected')); + }, + + troopCounterSelector: function ({ allegiance = 'attacker', troopNumber = 1 } = {}) { + return `.counter[data-allegiance="${allegiance}"][data-number="${troopNumber}"]`; + }, + + findMapResourceEl: function (driver) { + return driver.findElement(By.css('object')); + }, + + findTroopRecordEl: function (driver) { + return driver.findElement(By.css('.soldier-record')); + } +}; + +global.mockResponse = async function mockResponse(driver, urlPath, template, createFn) { + const connection = driver.createCDPConnection('page'); + const dom = new JSDOM(template); + const contents = createFn(dom.window.document); + + const httpResponse = new HttpResponse(url(urlPath)); + httpResponse.body = contents; + httpResponse.addHeaders('Content-Type', 'image/svg+xml'); + + await driver.onIntercept(await connection, httpResponse, async () => {}); +} + +global.createTroopCounter = function ( + allegiance = 'attacker', + troopNumber = 1, + squadNumber = 1, + weapon = 'rifle' +) { + return JSDOM.fragment(` + + + + + + `); +} + +global.placeCounter = function (document, counter, { x, y }) { + document.querySelector(`g[data-y="${y}"] g[data-x="${x}"]`).append(counter); +} + +global.selectCounter = function (counter) { + counter.querySelector('.counter').classList.add('selected'); + return counter; +} diff --git a/test/integration/select.test.js b/test/integration/select.test.js index d9b632e..acde0f0 100644 --- a/test/integration/select.test.js +++ b/test/integration/select.test.js @@ -1,136 +1,118 @@ -const { Builder, By, until } = require('selenium-webdriver'), +const { Builder, By } = require('selenium-webdriver'), chrome = require('selenium-webdriver/chrome.js'), chromeOptions = new chrome.Options(), - { readFile, readdir } = require('node:fs/promises'), - { HttpResponse } = require('selenium-webdriver/devtools/networkinterceptor'); + { readFile, readdir } = require('node:fs/promises'); chromeOptions.addArguments('--headless', '--disable-gpu', '--no-sandbox'); const buildPath = 'build/assets/images'; const defaultScenario = 'scenario-side_show'; -const fixtureFilePath = './test/integration/fixtures/scenario-test.svg'; +const fixtureFilePath = './test/integration/fixtures/scenario.svg'; -const selected = expect.stringContaining('selected'); -const notSelected = expect.not.stringContaining('selected'); - -let driver, httpResponse; +let driver, fixture, scenario; beforeAll(async () => { - const filenames = await readdir(buildPath); - const scenario = filenames.find(filename => filename.includes(defaultScenario)); - - httpResponse = new HttpResponse(url(`/assets/images/${scenario}`)); - httpResponse.body = await readFile(fixtureFilePath, 'utf8'); - httpResponse.addHeaders('Content-Type', 'image/svg+xml'); + const dirread = readdir(buildPath); + const fileRead = readFile(fixtureFilePath, 'utf8'); + const filenames = await dirread; + scenario = filenames.find(filename => filename.includes(defaultScenario)); + fixture = await fileRead; }); beforeEach(async () => { driver = new Builder().forBrowser('chrome').setChromeOptions(chromeOptions).build(); - const connection = await driver.createCDPConnection('page') - await driver.onIntercept(connection, httpResponse, async () => {}); - await driver.get(url('/')); - const mapPlaceholderEl = await driver.findElement(By.css('.map-placeholder')); - await driver.wait(until.elementIsNotVisible(mapPlaceholderEl), 1000); }); -it('selects and deselects a trooper by clicking on its counter', async () => { - const record = await driver.findElement(By.css('.soldier-record')); - const objectEl = await driver.findElement(By.css('object')); - const selector = '.counter[data-allegiance="attacker"][data-number="1"]'; - - expect(await record.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().frame(objectEl); - const svg = await driver.findElement(By.css('svg')); - const counter = await driver.findElement(By.css(selector), svg); - expect(await counter.getAttribute('class')).toEqual(notSelected); +describe('a trooper', () => { + beforeEach(async () => { + await mockResponse(driver, `/assets/images/${scenario}`, fixture, (document) => { + placeCounter(document, createTroopCounter(), { x: 1, y: 1 }); + return `\n` + document.querySelector('svg').outerHTML; + }); + }); - await counter.click(); + it('is selected by clicking on its counter', async () => { + await driver.get(url('/')); + await page.waitUntilMapLoaded(driver); - expect(await counter.getAttribute('class')).toEqual(selected); - await driver.switchTo().defaultContent(); - expect(await record.getAttribute('class')).toEqual(selected); + const record = page.findTroopRecordEl(driver); + const mapResource = page.findMapResourceEl(driver); - await driver.switchTo().frame(objectEl); - await counter.click(); + await page.expectNotSelected(await record); + const counter = await page.findElInFrame(await mapResource, By.css(page.troopCounterSelector())); + await page.expectNotSelected(counter); - expect(await counter.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().defaultContent(); - expect(await record.getAttribute('class')).toEqual(notSelected); -}); + await counter.click(); -it('selects and deselects trooper by clicking on its record', async () => { - const record = await driver.findElement(By.css('.soldier-record')); - const objectEl = await driver.findElement(By.css('object')); - const selector = '.counter[data-allegiance="attacker"][data-number="1"]'; + await page.expectSelected(counter); + await driver.switchTo().defaultContent(); + await page.expectSelected(await record); + }); - expect(await record.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().frame(objectEl); - const svg = await driver.findElement(By.css('svg')); - const counter = await driver.findElement(By.css(selector), svg); - expect(await counter.getAttribute('class')).toEqual(notSelected); + it('is selected by clicking on its record', async () => { + await driver.get(url('/')); + await page.waitUntilMapLoaded(driver); - await driver.switchTo().defaultContent(); - await record.click(); + const record = page.findTroopRecordEl(driver); + const mapResource = page.findMapResourceEl(driver); - expect(await record.getAttribute('class')).toEqual(selected); - await driver.switchTo().frame(objectEl); - expect(await counter.getAttribute('class')).toEqual(selected); + await page.expectNotSelected(await record); + const counter = await page.findElInFrame(await mapResource, By.css(page.troopCounterSelector())); + await page.expectNotSelected(counter); - await driver.switchTo().defaultContent(); - await record.click(); + await driver.switchTo().defaultContent(); + await record.click(); - expect(await record.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().frame(objectEl); - expect(await counter.getAttribute('class')).toEqual(notSelected); + await page.expectSelected(await record); + await driver.switchTo().frame(await mapResource); + await page.expectSelected(counter); + }); }); -it('selects a trooper by clicking on its counter and deselects it by clicking on its record', async () => { - const record = await driver.findElement(By.css('.soldier-record')); - const objectEl = await driver.findElement(By.css('object')); - const selector = '.counter[data-allegiance="attacker"][data-number="1"]'; - - expect(await record.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().frame(objectEl); - const svg = await driver.findElement(By.css('svg')); - const counter = await driver.findElement(By.css(selector), svg); - expect(await counter.getAttribute('class')).toEqual(notSelected); +describe('a selected trooper', () => { + beforeEach(async () => { + await mockResponse(driver, `/assets/images/${scenario}`, fixture, (document) => { + placeCounter(document, selectCounter(createTroopCounter()), { x: 1, y: 1 }); + return `\n` + document.querySelector('svg').outerHTML; + }); + }); - await counter.click(); + it('is deselected by clicking on its counter', async () => { + await driver.get(url('/')); + await page.waitUntilMapLoaded(driver); - expect(await counter.getAttribute('class')).toEqual(selected); - await driver.switchTo().defaultContent(); - expect(await record.getAttribute('class')).toEqual(selected); + const record = page.findTroopRecordEl(driver); + const mapResource = page.findMapResourceEl(driver); - await record.click(); + await page.expectSelected(await record); + const counter = await page.findElInFrame(await mapResource, By.css(page.troopCounterSelector())); + await page.expectSelected(counter); - expect(await record.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().frame(objectEl); - expect(await counter.getAttribute('class')).toEqual(notSelected); -}); + await counter.click(); -it('selects a trooper by clicking on its record and deselects it by clicking on its counter', async () => { - const record = await driver.findElement(By.css('.soldier-record')); - const objectEl = await driver.findElement(By.css('object')); - const selector = '.counter[data-allegiance="attacker"][data-number="1"]'; + await page.expectNotSelected(counter); + await driver.switchTo().defaultContent(); + await page.expectNotSelected(await record); + }); - expect(await record.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().frame(objectEl); - const svg = await driver.findElement(By.css('svg')); - const counter = await driver.findElement(By.css(selector), svg); - expect(await counter.getAttribute('class')).toEqual(notSelected); + it('is deselected by clicking on its record', async () => { + await driver.get(url('/')); + await page.waitUntilMapLoaded(driver); - await driver.switchTo().defaultContent(); - await record.click(); + const record = page.findTroopRecordEl(driver); + const mapResource = page.findMapResourceEl(driver); - expect(await record.getAttribute('class')).toEqual(selected); - await driver.switchTo().frame(objectEl); - expect(await counter.getAttribute('class')).toEqual(selected); + await page.expectSelected(await record); + const counter = await page.findElInFrame(await mapResource, By.css(page.troopCounterSelector())); + await page.expectSelected(counter); - await counter.click(); + await driver.switchTo().defaultContent(); + await record.click(); - expect(await counter.getAttribute('class')).toEqual(notSelected); - await driver.switchTo().defaultContent(); - expect(await record.getAttribute('class')).toEqual(notSelected); + await page.expectNotSelected(await record); + await driver.switchTo().frame(await mapResource); + await page.expectNotSelected(counter); + }); }); afterEach(async () => { -- cgit v1.2.3