diff --git a/keepassxc-browser/tests/assert.js b/keepassxc-browser/tests/assert.js deleted file mode 100644 index cd21962..0000000 --- a/keepassxc-browser/tests/assert.js +++ /dev/null @@ -1,110 +0,0 @@ -'use strict'; - -function kpxcAssert(func, expected, card, testName) { - if (func === expected) { - createResult(card, true, `Test passed: ${testName}`); - return; - } - - createResult(card, false, `Test failed: ${testName}. Result is: ${func}`); -} - -function assertRegex(func, expected, card, testName) { - if ((func === null && expected === false) - || (func && (func.length > 0) === expected)) { - createResult(card, true, `Test passed: ${testName}`); - return; - } - - createResult(card, false, `Test failed: ${testName}. Result is: ${func}`); -} - -async function assertInputFields(localFile, expectedFieldCount, actionElementId) { - return new Promise((resolve) => { - const iframe = document.getElementById('testFile'); - iframe.src = localFile; - - const iframeLoaded = function() { - const frameContent = iframe.contentWindow.document.getElementsByTagName('body')[0]; - - // Load prototypes to iframe. This doesn't work automatically from ui.js - iframe.contentWindow.Element.prototype.getLowerCaseAttribute = function(attr) { - return this.getAttribute(attr) ? this.getAttribute(attr).toLowerCase() : undefined; - }; - - // An user interaction is required before testing - if (actionElementId) { - const actionElement = frameContent.querySelector(actionElementId); - if (actionElement) { - actionElement.click(); - } - } - - const inputs = kpxcObserverHelper.getInputs(frameContent); - kpxcAssert(inputs.length, expectedFieldCount, Tests.INPUT_FIELDS, `getInputs() for ${localFile} with ${expectedFieldCount} fields`); - iframe.removeEventListener('load', iframeLoaded); - resolve(); - }; - - // Wait for iframe to load - iframe.addEventListener('load', iframeLoaded); - }); -} - -async function assertPasswordChangeFields(localFile, expectedNewPassword) { - return new Promise((resolve) => { - const iframe = document.getElementById('testFile'); - iframe.src = localFile; - - const iframeLoaded = function() { - const frameContent = iframe.contentWindow.document.getElementsByTagName('body')[0]; - - // Load prototypes to iframe. This doesn't work automatically from ui.js - iframe.contentWindow.Element.prototype.getLowerCaseAttribute = function(attr) { - return this.getAttribute(attr) ? this.getAttribute(attr).toLowerCase() : undefined; - }; - - const inputs = kpxcObserverHelper.getInputs(frameContent, true); - const newPassword = kpxcForm.getNewPassword(inputs); - kpxcAssert(newPassword, expectedNewPassword, Tests.PASSWORD_CHANGE, `New password matches for ${localFile}`); - iframe.removeEventListener('load', iframeLoaded); - resolve(); - }; - - // Wait for iframe to load - iframe.addEventListener('load', iframeLoaded); - }); -} - -async function assertTOTPField(classStr, properties, testName, expectedResult) { - const input = kpxcUI.createElement('input', classStr, properties); - document.body.appendChild(input); - - const isAccepted = kpxcTOTPIcons.isAcceptedTOTPField(input); - const isValid = kpxcTOTPIcons.isValid(input); - - document.body.removeChild(input); - kpxcAssert(isAccepted && isValid, expectedResult, Tests.TOTP_FIELDS, testName); -} - -async function assertSearchField(classStr, properties, testName, expectedResult) { - const input = kpxcUI.createElement('input', classStr, properties); - document.body.appendChild(input); - - const isSearchfield = kpxcFields.isSearchField(input); - - document.body.removeChild(input); - kpxcAssert(isSearchfield, expectedResult, Tests.SEARCH_FIELDS, testName); -} - -async function assertSearchForm(properties, testName, expectedResult) { - const form = kpxcUI.createElement('form', '', { action: 'search' }); - const input = kpxcUI.createElement('input', '', properties); - form.appendChild(input); - document.body.appendChild(form); - - const isSearchfield = kpxcFields.isSearchField(input); - - document.body.removeChild(form); - kpxcAssert(isSearchfield, expectedResult, Tests.SEARCH_FIELDS, testName); -} diff --git a/keepassxc-browser/tests/basic1.html b/keepassxc-browser/tests/basic1.html deleted file mode 100644 index 5c16408..0000000 --- a/keepassxc-browser/tests/basic1.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/keepassxc-browser/tests/example.spec.js b/keepassxc-browser/tests/example.spec.js deleted file mode 100644 index 75633bb..0000000 --- a/keepassxc-browser/tests/example.spec.js +++ /dev/null @@ -1,75 +0,0 @@ -'use strict'; - -const { chromium, test, expect } = require('@playwright/test'); -const fileUrl = require('file-url'); - -const DEST = 'keepassxc-browser/tests'; - -test.beforeEach(async ({ page }) => { - await page.goto(fileUrl(`${DEST}/tests.html`)); -}); - -test.describe('Content script tests', () => { - test('General tests', async ({ page }) => { - const resultCount = await page.locator('css=#general-results >> css=.fa').count(); - await expect.soft(resultCount).toBeGreaterThan(0); - - for (var i = 0; i < resultCount; i++) { - const elem = await page.locator('css=#general-results >> css=.fa').nth(i); - const id = await elem.getAttribute('id'); - await expect.soft(elem, id).toHaveClass('fa fa-check'); - } - }); - - test('Input field matching tests', async ({ page }) => { - const resultCount = await page.locator('css=#input-field-results >> css=.fa').count(); - await expect.soft(resultCount).toBeGreaterThan(0); - - for (var i = 0; i < resultCount; i++) { - const elem = await page.locator('css=#input-field-results >> css=.fa').nth(i); - const id = await elem.getAttribute('id'); - await expect.soft(elem, id).toHaveClass('fa fa-check'); - } - }); - - test('Search field tests', async ({ page }) => { - const resultCount = await page.locator('css=#search-field-results >> css=.fa').count(); - await expect.soft(resultCount).toBeGreaterThan(0); - - for (var i = 0; i < resultCount; i++) { - const elem = await page.locator('css=#search-field-results >> css=.fa').nth(i); - const id = await elem.getAttribute('id'); - await expect.soft(elem, id).toHaveClass('fa fa-check'); - } - }); - - test('TOTP field tests', async ({ page }) => { - const resultCount = await page.locator('css=#totp-field-results >> css=.fa').count(); - await expect.soft(resultCount).toBeGreaterThan(0); - - for (var i = 0; i < resultCount; i++) { - const elem = await page.locator('css=#totp-field-results >> css=.fa').nth(i); - const id = await elem.getAttribute('id'); - await expect.soft(elem, id).toHaveClass('fa fa-check'); - } - }); - - test('Password change tests', async ({ page }) => { - const resultCount = await page.locator('css=#password-change-results >> css=.fa').count(); - await expect.soft(resultCount).toBeGreaterThan(0); - - for (var i = 0; i < resultCount; i++) { - const elem = await page.locator('css=#password-change-results >> css=.fa').nth(i); - const id = await elem.getAttribute('id'); - await expect.soft(elem, id).toHaveClass('fa fa-check'); - } - }); -}); - -const verifyResults = async(page, selector) => { - const resultCount = await page.locator(`css=#${selector} >> css=.fa`).count(); - - const elem = await page.locator(`css=#${selector} >> css=.fa`).nth(0); - const id = await elem.getAttribute('id'); - await expect.soft(elem, id).toHaveClass('fa fa-check'); -}; diff --git a/keepassxc-browser/tests/global-setup.js b/keepassxc-browser/tests/global-setup.js deleted file mode 100644 index 0411044..0000000 --- a/keepassxc-browser/tests/global-setup.js +++ /dev/null @@ -1,9 +0,0 @@ -const fs = require('fs-extra'); - -const DEST = 'keepassxc-browser/tests'; - -module.exports = async config => { - // Create a temporary directory and copy tests/* to keepassxc-browser/tests - await fs.ensureDir(DEST); - await fs.copy('./tests', DEST); -}; diff --git a/keepassxc-browser/tests/global-teardown.js b/keepassxc-browser/tests/global-teardown.js deleted file mode 100644 index 65fc8bb..0000000 --- a/keepassxc-browser/tests/global-teardown.js +++ /dev/null @@ -1,8 +0,0 @@ -const fs = require('fs-extra'); - -const DEST = 'keepassxc-browser/tests'; - -module.exports = async config => { - // Delete previously created temporary directory. Comment for re-running tests manually inside the extension. - await fs.remove(DEST); -}; diff --git a/keepassxc-browser/tests/html/basic1.html b/keepassxc-browser/tests/html/basic1.html deleted file mode 100644 index 5c16408..0000000 --- a/keepassxc-browser/tests/html/basic1.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/keepassxc-browser/tests/html/basic2.html b/keepassxc-browser/tests/html/basic2.html deleted file mode 100644 index b417524..0000000 --- a/keepassxc-browser/tests/html/basic2.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/keepassxc-browser/tests/html/basic3.html b/keepassxc-browser/tests/html/basic3.html deleted file mode 100644 index a2aba6a..0000000 --- a/keepassxc-browser/tests/html/basic3.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/keepassxc-browser/tests/html/basic4.html b/keepassxc-browser/tests/html/basic4.html deleted file mode 100644 index 8f199b8..0000000 --- a/keepassxc-browser/tests/html/basic4.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/keepassxc-browser/tests/html/basictest.html b/keepassxc-browser/tests/html/basictest.html deleted file mode 100644 index fd152f0..0000000 --- a/keepassxc-browser/tests/html/basictest.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/keepassxc-browser/tests/html/div1.html b/keepassxc-browser/tests/html/div1.html deleted file mode 100644 index ae9ffb3..0000000 --- a/keepassxc-browser/tests/html/div1.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/keepassxc-browser/tests/html/div1.js b/keepassxc-browser/tests/html/div1.js deleted file mode 100644 index ffdd4d0..0000000 --- a/keepassxc-browser/tests/html/div1.js +++ /dev/null @@ -1,11 +0,0 @@ -'use script'; - -document.getElementById('toggle').addEventListener('click', function(e) { - const loginForm = document.getElementById('loginForm'); - - if (loginForm.style.display === 'none') { - loginForm.style.display = 'block'; - } else { - loginForm.style.display = 'none'; - } -}); diff --git a/keepassxc-browser/tests/html/div2.html b/keepassxc-browser/tests/html/div2.html deleted file mode 100644 index 86b8669..0000000 --- a/keepassxc-browser/tests/html/div2.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - -
-
-
- -
-
-
- - - diff --git a/keepassxc-browser/tests/html/div2.js b/keepassxc-browser/tests/html/div2.js deleted file mode 100644 index a76c91b..0000000 --- a/keepassxc-browser/tests/html/div2.js +++ /dev/null @@ -1,18 +0,0 @@ -'use script'; - -document.getElementById('toggle').addEventListener('click', function(e) { - const dialog = document.getElementById('dialog'); - const outer = document.getElementById('outer'); - const inner = document.getElementById('inner'); - - if (dialog.style.zIndex === 'auto') { - dialog.style.zIndex = 9999; - inner.style.margin = '0px'; - outer.style.height = 'auto'; - - } else { - dialog.style.zIndex = 'auto'; - inner.style.margin = '-197px'; - outer.style.height = '0px'; - } -}); diff --git a/keepassxc-browser/tests/html/div3.html b/keepassxc-browser/tests/html/div3.html deleted file mode 100644 index 5567667..0000000 --- a/keepassxc-browser/tests/html/div3.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/keepassxc-browser/tests/html/div4.html b/keepassxc-browser/tests/html/div4.html deleted file mode 100644 index 909ba7c..0000000 --- a/keepassxc-browser/tests/html/div4.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/keepassxc-browser/tests/html/hidden_fields1.html b/keepassxc-browser/tests/html/hidden_fields1.html deleted file mode 100644 index 84d3da8..0000000 --- a/keepassxc-browser/tests/html/hidden_fields1.html +++ /dev/null @@ -1,12 +0,0 @@ - - -
- -
- -
- -
- - - diff --git a/keepassxc-browser/tests/html/hidden_fields2.html b/keepassxc-browser/tests/html/hidden_fields2.html deleted file mode 100644 index d5b865a..0000000 --- a/keepassxc-browser/tests/html/hidden_fields2.html +++ /dev/null @@ -1,18 +0,0 @@ - - - -
- - - - - - - -
- - diff --git a/keepassxc-browser/tests/html/passwordchange1.html b/keepassxc-browser/tests/html/passwordchange1.html deleted file mode 100644 index c03ca47..0000000 --- a/keepassxc-browser/tests/html/passwordchange1.html +++ /dev/null @@ -1,7 +0,0 @@ - - -
-
-
- - diff --git a/keepassxc-browser/tests/html/passwordchange2.html b/keepassxc-browser/tests/html/passwordchange2.html deleted file mode 100644 index 8cc0120..0000000 --- a/keepassxc-browser/tests/html/passwordchange2.html +++ /dev/null @@ -1,7 +0,0 @@ - - -
-
-
- - diff --git a/keepassxc-browser/tests/html/passwordchange3.html b/keepassxc-browser/tests/html/passwordchange3.html deleted file mode 100644 index d3453f8..0000000 --- a/keepassxc-browser/tests/html/passwordchange3.html +++ /dev/null @@ -1,10 +0,0 @@ - - -
-
-
-
-
-
- - diff --git a/keepassxc-browser/tests/html/passwordchange4.html b/keepassxc-browser/tests/html/passwordchange4.html deleted file mode 100644 index 88adfbf..0000000 --- a/keepassxc-browser/tests/html/passwordchange4.html +++ /dev/null @@ -1,10 +0,0 @@ - - -
-
-
-
-
-
- - diff --git a/keepassxc-browser/tests/html/passwordchange5.html b/keepassxc-browser/tests/html/passwordchange5.html deleted file mode 100644 index 6df49e8..0000000 --- a/keepassxc-browser/tests/html/passwordchange5.html +++ /dev/null @@ -1,17 +0,0 @@ - - - -
-
-
- -
-
-
- -
-
-
- - - diff --git a/keepassxc-browser/tests/tests.html b/keepassxc-browser/tests/tests.html deleted file mode 100644 index b37db26..0000000 --- a/keepassxc-browser/tests/tests.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - --> - - - - - - - - - -
-
-
-
- - -
-

Content script tests

- -
-
General (global.js)
-
-
- -
-
Input field matching (keepassxc-browser.js)
-
-
- -
-
Search fields (keepassxc-browser.js)
-
-
- -
-
TOTP fields (totp-field.js)
-
-
- -
-
Password change (keepassxc-browser.js)
-
-
- -
- - -
-
-
- - diff --git a/keepassxc-browser/tests/tests.js b/keepassxc-browser/tests/tests.js deleted file mode 100644 index 59b3458..0000000 --- a/keepassxc-browser/tests/tests.js +++ /dev/null @@ -1,168 +0,0 @@ -'use strict'; - -const Tests = { - GENERAL: '#general-results', - INPUT_FIELDS: '#input-field-results', - TOTP_FIELDS: '#totp-field-results', - SEARCH_FIELDS: '#search-field-results', - PASSWORD_CHANGE: '#password-change-results', -}; - -function createResult(card, res, text) { - const icon = kpxcUI.createElement('i', res ? 'fa fa-check' : 'fa fa-close', { id: text }); - const span = kpxcUI.createElement('span', '', '', text); - const br = document.createElement('br'); - - document.querySelector(card).appendMultiple(icon, span, br); -} - -// General (global.js) -async function testGeneral() { - const testCard = Tests.GENERAL; - - // General - kpxcAssert(trimURL('https://test.com/path_to_somwhere?login=username'), 'https://test.com/path_to_somwhere', testCard, 'trimURL()'); - assertRegex(slashNeededForUrl('https://test.com'), true, testCard, 'slashNeededForUrl()'); - assertRegex(slashNeededForUrl('https://test.com/'), false, testCard, 'slashNeededForUrl()'); - - // URL matching (URL in Site Preferences, page URL, expected result). - // Consider using slighly different URL's for the tests cases. - const matches = [ - [ 'https://example.com/*', 'https://example.com/login_page', true ], - [ 'https://example.com/*', 'https://example2.com/login_page', false ], - [ 'https://example.com/*', 'https://subdomain.example.com/login_page', false ], - [ 'https://*.example.com/*', 'https://example.com/login_page', true ], - [ 'https://*.example.com/*', 'https://test.example.com/login_page', true ], - [ 'https://test.example.com/*', 'https://subdomain.example.com/login_page', false ], - [ 'https://test.example.com/page/*', 'https://test.example.com/page/login_page', true ], - [ 'https://test.example.com/page/another_page/*', 'https://test.example.com/page/login', false ], - [ 'https://test.example.com/path/another/a/', 'https://test.example.com/path/another/a/', true ], - [ 'https://test.example.com/path/another/a/', 'https://test.example.com/path/another/b/', false ], - ]; - - for (const m of matches) { - assertRegex(siteMatch(m[0], m[1]), m[2], testCard, `siteMatch() for ${m[1]}`); - } - - // Base domain parsing (window.location.hostname) - const domains = [ - [ 'another.example.co.uk', 'example.co.uk' ], - [ 'www.example.com', 'example.com' ], - [ 'test.net', 'test.net' ], - [ 'so.many.subdomains.co.jp', 'subdomains.co.jp' ], - [ 'test.site.example.com.au', 'example.com.au' ], - [ '192.168.0.1', '192.168.0.1' ] - ]; - - for (const d of domains) { - kpxcAssert(getTopLevelDomainFromUrl(d[0]), d[1], testCard, 'getBaseDomainFromUrl() for ' + d[0]); - } -} - -// Input field matching (keepassxc-browser.js) -async function testInputFields() { - // Local filename, expected fields, action element ID (a button to be clicked) - const localFiles = [ - [ 'html/basic1.html', 2 ], // Username/passwd fields - [ 'html/basic2.html', 1 ], // Only username field - [ 'html/basic3.html', 1 ], // Only password field - [ 'html/basic4.html', 3 ], // Username/passwd/TOTP fields - [ 'html/div1.html', 2, '#toggle' ], // Fields are behind a button that must be pressed - [ 'html/div2.html', 2, '#toggle' ], // Fields are behind a button that must be pressed behind a JavaScript - [ 'html/div3.html', 2, '#toggle' ], // Fields are behind a button that must be pressed - [ 'html/div4.html', 2, '#toggle' ], // Fields are behind a button that must be pressed - [ 'html/hidden_fields1.html', 0 ], // Two hidden fields - [ 'html/hidden_fields2.html', 1 ], // Two hidden fields with one visible - ]; - - for (const file of localFiles) { - await assertInputFields(file[0], file[1], file[2]); - } - - document.getElementById('testFile').hidden = true; -} - -// Search fields (kpxcFields -async function testSearchFields() { - const searchFields = [ - [ '', { id: 'otp_field', name: 'otp', type: 'text', maxLength: '8' }, 'Generic 2FA field', false ], - [ '', { placeholder: 'search', type: 'text', id: 'username' }, 'Placeholder only', true ], - [ '', { ariaLabel: 'search', type: 'text', id: 'username' }, 'aria-label only', true ], - - ]; - - for (const field of searchFields) { - assertSearchField(field[0], field[1], field[2], field[3]); - } - - assertSearchForm({ id: 'username', type: 'text', }, 'Generic input field under search form', true); -} - -// TOTP fields (kpxcTOTPIcons) -async function testTotpFields() { - const totpFields = [ - [ '', { id: 'otp_field', name: 'otp', type: 'text', maxLength: '8' }, 'Generic 2FA field', true ], - [ '', { id: '2fa', type: 'text', maxLength: '6' }, 'Generic 2FA field', true ], - [ '', { id: '2fa', type: 'text', maxLength: '4' }, 'Ignore if field maxLength too small', false ], - [ '', { id: '2fa', type: 'text', maxLength: '12' }, 'Ignore if field maxLength too long', false ], - [ '', { id: 'username', type: 'text', }, 'Ignore a generic input field', false ], - [ '', { type: 'password', }, 'Ignore a password input field', false ], - [ // Protonmail - 'TwoFA-input ng-empty ng-invalid ng-invalid-required ng-valid-minlength ng-valid-maxlength ng-touched', - { autocapitalize: 'off', autocorrect: 'off', id: 'twoFactorCode', type: 'text', placeholder: 'Two-factor passcode', name: 'twoFactorCode' }, - 'Protonmail 2FA', - true - ], - [ // Nextcloud - '', - { minlength: '6', maxLength: '10', name: 'challenge', placeholder: 'Authentication code', type: 'tel', }, - 'Nextcloud 2FA', - true - ], - [ // GMail - 'whsOnd zHQkBf', - { autocomplete: 'off', id: 'idvPin', tabindex: '0', name: 'idvPin', pattern: '[0-9 ]*', type: 'tel', spellcheck: 'false' }, - 'GMail 2FA', - true - ], - [ // Live.com - 'form-control', - { autocomplete: 'off', id: 'idTxtBx_SAOTCC_OTC', maxLength: '8', tabindex: '0', name: 'otc', placeholder: 'Code', type: 'tel' }, - 'Live.com 2FA', - true - ], - ]; - - for (const field of totpFields) { - assertTOTPField(field[0], field[1], field[2], field[3]); - } -} - -// Password change -async function testPasswordChange() { - // Local filename, expected new password - const localFiles = [ - [ 'html/passwordchange1.html', 'newPassword' ], // Default order without form - [ 'html/passwordchange2.html', 'newPassword' ], // Reversed order without form - [ 'html/passwordchange3.html', 'newPassword' ], // Default order with form - [ 'html/passwordchange4.html', 'newPassword' ], // Reversed order with form - [ 'html/passwordchange5.html', 'newPassword' ], // Each field has own form - ]; - - for (const file of localFiles) { - await assertPasswordChangeFields(file[0], file[1]); - } - - document.getElementById('testFile').hidden = true; -} - -// Run tests -(async () => { - await Promise.all([ - await testGeneral(), - await testInputFields(), - await testSearchFields(), - await testTotpFields(), - await testPasswordChange(), - ]); -})(); diff --git a/package.json b/package.json index 5d211f0..6b679bb 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ }, "scripts": { "build": "node build.js", - "tests": "npx playwright test" + "tests": "npx playwright test tests/tests.js" }, "repository": { "type": "git", diff --git a/playwright.config.js b/playwright.config.js index 72e3a1f..012b7ff 100644 --- a/playwright.config.js +++ b/playwright.config.js @@ -8,7 +8,7 @@ const config = { }, forbidOnly: !!process.env.CI, globalSetup: require.resolve('./tests/global-setup'), - //globalTeardown: require.resolve('./tests/global-teardown'), + globalTeardown: require.resolve('./tests/global-teardown'), retries: process.env.CI ? 2 : 0, workers: process.env.CI ? 1 : undefined, reporter: 'list', @@ -23,12 +23,12 @@ const config = { ...devices['Desktop Chrome'], }, }, - /*{ + { name: 'firefox', use: { ...devices['Desktop Firefox'], }, - },*/ + }, ], }; diff --git a/tests/assert.js b/tests/assert.js index cd21962..2e0cc23 100644 --- a/tests/assert.js +++ b/tests/assert.js @@ -19,60 +19,38 @@ function assertRegex(func, expected, card, testName) { createResult(card, false, `Test failed: ${testName}. Result is: ${func}`); } -async function assertInputFields(localFile, expectedFieldCount, actionElementId) { - return new Promise((resolve) => { - const iframe = document.getElementById('testFile'); - iframe.src = localFile; +async function assertInputFields(localDiv, expectedFieldCount, actionElementId) { + return new Promise(async (resolve) => { + const div = document.getElementById(localDiv); + div.style.display = 'block'; - const iframeLoaded = function() { - const frameContent = iframe.contentWindow.document.getElementsByTagName('body')[0]; - - // Load prototypes to iframe. This doesn't work automatically from ui.js - iframe.contentWindow.Element.prototype.getLowerCaseAttribute = function(attr) { - return this.getAttribute(attr) ? this.getAttribute(attr).toLowerCase() : undefined; - }; - - // An user interaction is required before testing - if (actionElementId) { - const actionElement = frameContent.querySelector(actionElementId); - if (actionElement) { - actionElement.click(); - } + // An user interaction is required before testing + if (actionElementId) { + const actionElement = div.querySelector(actionElementId); + if (actionElement) { + actionElement.click(); } + } - const inputs = kpxcObserverHelper.getInputs(frameContent); - kpxcAssert(inputs.length, expectedFieldCount, Tests.INPUT_FIELDS, `getInputs() for ${localFile} with ${expectedFieldCount} fields`); - iframe.removeEventListener('load', iframeLoaded); - resolve(); - }; + const inputs = kpxcObserverHelper.getInputs(div); + kpxcAssert(inputs.length, expectedFieldCount, Tests.INPUT_FIELDS, `getInputs() for ${localDiv} with ${expectedFieldCount} fields`); - // Wait for iframe to load - iframe.addEventListener('load', iframeLoaded); + div.style.display = 'none'; + resolve(); }); } -async function assertPasswordChangeFields(localFile, expectedNewPassword) { - return new Promise((resolve) => { - const iframe = document.getElementById('testFile'); - iframe.src = localFile; +async function assertPasswordChangeFields(localDiv, expectedNewPassword) { + return new Promise(async (resolve) => { + const div = document.getElementById(localDiv); + div.style.display = 'block'; - const iframeLoaded = function() { - const frameContent = iframe.contentWindow.document.getElementsByTagName('body')[0]; + const inputs = kpxcObserverHelper.getInputs(div, true); + const newPassword = kpxcForm.getNewPassword(inputs); + kpxcAssert(newPassword, expectedNewPassword, Tests.PASSWORD_CHANGE, `New password matches for ${localDiv}`); - // Load prototypes to iframe. This doesn't work automatically from ui.js - iframe.contentWindow.Element.prototype.getLowerCaseAttribute = function(attr) { - return this.getAttribute(attr) ? this.getAttribute(attr).toLowerCase() : undefined; - }; - - const inputs = kpxcObserverHelper.getInputs(frameContent, true); - const newPassword = kpxcForm.getNewPassword(inputs); - kpxcAssert(newPassword, expectedNewPassword, Tests.PASSWORD_CHANGE, `New password matches for ${localFile}`); - iframe.removeEventListener('load', iframeLoaded); - resolve(); - }; - - // Wait for iframe to load - iframe.addEventListener('load', iframeLoaded); + div.style.display = 'none'; + resolve(); }); } diff --git a/tests/example.spec.js b/tests/example.spec.js index 75633bb..7e89283 100644 --- a/tests/example.spec.js +++ b/tests/example.spec.js @@ -1,6 +1,6 @@ 'use strict'; -const { chromium, test, expect } = require('@playwright/test'); +const { test, expect } = require('@playwright/test'); const fileUrl = require('file-url'); const DEST = 'keepassxc-browser/tests'; diff --git a/tests/html/basic1.html b/tests/html/basic1.html deleted file mode 100644 index 5c16408..0000000 --- a/tests/html/basic1.html +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/tests/html/basic2.html b/tests/html/basic2.html deleted file mode 100644 index b417524..0000000 --- a/tests/html/basic2.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/tests/html/basic3.html b/tests/html/basic3.html deleted file mode 100644 index a2aba6a..0000000 --- a/tests/html/basic3.html +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/tests/html/basic4.html b/tests/html/basic4.html deleted file mode 100644 index 8f199b8..0000000 --- a/tests/html/basic4.html +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/tests/html/basictest.html b/tests/html/basictest.html deleted file mode 100644 index fd152f0..0000000 --- a/tests/html/basictest.html +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/tests/html/div1.html b/tests/html/div1.html deleted file mode 100644 index ae9ffb3..0000000 --- a/tests/html/div1.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/tests/html/div2.html b/tests/html/div2.html deleted file mode 100644 index 86b8669..0000000 --- a/tests/html/div2.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - -
-
-
- -
-
-
- - - diff --git a/tests/html/div3.html b/tests/html/div3.html deleted file mode 100644 index 5567667..0000000 --- a/tests/html/div3.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/tests/html/div3.js b/tests/html/div3.js deleted file mode 100644 index 7d74e14..0000000 --- a/tests/html/div3.js +++ /dev/null @@ -1,26 +0,0 @@ -'use script'; - -document.getElementById('toggle').addEventListener('click', function(e) { - const loginForm = document.getElementById('loginForm'); - - if (!loginForm) { - const dialog = document.createElement('div'); - dialog.setAttribute('id', 'loginForm'); - - const usernameInput = document.createElement('input'); - usernameInput.setAttribute('type', 'text'); - usernameInput.setAttribute('name', 'loginField'); - usernameInput.setAttribute('placeholder', 'username'); - dialog.append(usernameInput); - - const passwordInput = document.createElement('input'); - passwordInput.setAttribute('type', 'password'); - passwordInput.setAttribute('name', 'passwordField'); - passwordInput.setAttribute('placeholder', 'password'); - dialog.append(passwordInput); - - document.body.appendChild(dialog); - } else { - document.body.removeChild(loginForm); - } -}); diff --git a/tests/html/div4.html b/tests/html/div4.html deleted file mode 100644 index 909ba7c..0000000 --- a/tests/html/div4.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/tests/html/div4.js b/tests/html/div4.js deleted file mode 100644 index db7db8f..0000000 --- a/tests/html/div4.js +++ /dev/null @@ -1,55 +0,0 @@ -'use script'; - -document.getElementById('toggle').addEventListener('click', function(e) { - const loginForm = document.getElementById('loginForm'); - - if (!loginForm) { - const dialog = document.createElement('div'); - dialog.setAttribute('id', 'loginForm'); - dialog.style.position = 'fixed'; - dialog.style.zIndex = '1002'; - - const wrapper = document.createElement('div'); - wrapper.setAttribute('tabIndex', '-1'); - wrapper.style.height = '100%'; - wrapper.style.width = '100%'; - wrapper.style.outline = '0px'; - wrapper.style.overflow = 'visible'; - - const innerDiv = document.createElement('div'); - innerDiv.setAttribute('id', 'innerDiv'); - - const contentDiv = document.createElement('div'); - contentDiv.setAttribute('id', 'contentDiv'); - - const form = document.createElement('form'); - form.setAttribute('action', 'loginUser'); - form.setAttribute('method', 'post'); - - const divUsernameWithLabel = document.createElement('div'); - const divPasswordWithLabel = document.createElement('div'); - - const usernameInput = document.createElement('input'); - usernameInput.setAttribute('type', 'text'); - usernameInput.setAttribute('name', 'loginField'); - usernameInput.setAttribute('placeholder', 'username'); - divUsernameWithLabel.append(usernameInput); - - const passwordInput = document.createElement('input'); - passwordInput.setAttribute('type', 'password'); - passwordInput.setAttribute('name', 'passwordField'); - passwordInput.setAttribute('placeholder', 'password'); - divPasswordWithLabel.append(passwordInput); - - form.append(divUsernameWithLabel); - form.append(divPasswordWithLabel); - contentDiv.append(form); - innerDiv.append(contentDiv); - wrapper.append(innerDiv); - dialog.append(wrapper); - - document.body.appendChild(dialog); - } else { - document.body.removeChild(loginForm); - } -}); diff --git a/tests/html/hidden_fields1.html b/tests/html/hidden_fields1.html deleted file mode 100644 index 84d3da8..0000000 --- a/tests/html/hidden_fields1.html +++ /dev/null @@ -1,12 +0,0 @@ - - -
- -
- -
- -
- - - diff --git a/tests/html/hidden_fields2.html b/tests/html/hidden_fields2.html deleted file mode 100644 index d5b865a..0000000 --- a/tests/html/hidden_fields2.html +++ /dev/null @@ -1,18 +0,0 @@ - - - -
- - - - - - - -
- - diff --git a/tests/html/passwordchange1.html b/tests/html/passwordchange1.html deleted file mode 100644 index c03ca47..0000000 --- a/tests/html/passwordchange1.html +++ /dev/null @@ -1,7 +0,0 @@ - - -
-
-
- - diff --git a/tests/html/passwordchange2.html b/tests/html/passwordchange2.html deleted file mode 100644 index 8cc0120..0000000 --- a/tests/html/passwordchange2.html +++ /dev/null @@ -1,7 +0,0 @@ - - -
-
-
- - diff --git a/tests/html/passwordchange3.html b/tests/html/passwordchange3.html deleted file mode 100644 index d3453f8..0000000 --- a/tests/html/passwordchange3.html +++ /dev/null @@ -1,10 +0,0 @@ - - -
-
-
-
-
-
- - diff --git a/tests/html/passwordchange4.html b/tests/html/passwordchange4.html deleted file mode 100644 index 88adfbf..0000000 --- a/tests/html/passwordchange4.html +++ /dev/null @@ -1,10 +0,0 @@ - - -
-
-
-
-
-
- - diff --git a/tests/html/passwordchange5.html b/tests/html/passwordchange5.html deleted file mode 100644 index 6df49e8..0000000 --- a/tests/html/passwordchange5.html +++ /dev/null @@ -1,17 +0,0 @@ - - - -
-
-
- -
-
-
- -
-
-
- - - diff --git a/tests/html/div1.js b/tests/scripts/div1.js similarity index 55% rename from tests/html/div1.js rename to tests/scripts/div1.js index ffdd4d0..5b378b7 100644 --- a/tests/html/div1.js +++ b/tests/scripts/div1.js @@ -1,7 +1,7 @@ 'use script'; -document.getElementById('toggle').addEventListener('click', function(e) { - const loginForm = document.getElementById('loginForm'); +document.getElementById('toggle1').addEventListener('click', function(e) { + const loginForm = document.getElementById('loginForm1'); if (loginForm.style.display === 'none') { loginForm.style.display = 'block'; diff --git a/tests/html/div2.js b/tests/scripts/div2.js similarity index 86% rename from tests/html/div2.js rename to tests/scripts/div2.js index a76c91b..c6ff46d 100644 --- a/tests/html/div2.js +++ b/tests/scripts/div2.js @@ -1,6 +1,6 @@ 'use script'; -document.getElementById('toggle').addEventListener('click', function(e) { +document.getElementById('toggle2').addEventListener('click', function(e) { const dialog = document.getElementById('dialog'); const outer = document.getElementById('outer'); const inner = document.getElementById('inner'); @@ -9,7 +9,6 @@ document.getElementById('toggle').addEventListener('click', function(e) { dialog.style.zIndex = 9999; inner.style.margin = '0px'; outer.style.height = 'auto'; - } else { dialog.style.zIndex = 'auto'; inner.style.margin = '-197px'; diff --git a/keepassxc-browser/tests/html/div3.js b/tests/scripts/div3.js similarity index 81% rename from keepassxc-browser/tests/html/div3.js rename to tests/scripts/div3.js index 7d74e14..00bdb8f 100644 --- a/keepassxc-browser/tests/html/div3.js +++ b/tests/scripts/div3.js @@ -1,6 +1,6 @@ 'use script'; -document.getElementById('toggle').addEventListener('click', function(e) { +document.getElementById('toggle3').addEventListener('click', function(e) { const loginForm = document.getElementById('loginForm'); if (!loginForm) { @@ -19,8 +19,6 @@ document.getElementById('toggle').addEventListener('click', function(e) { passwordInput.setAttribute('placeholder', 'password'); dialog.append(passwordInput); - document.body.appendChild(dialog); - } else { - document.body.removeChild(loginForm); + e.currentTarget.parentElement.appendChild(dialog); } }); diff --git a/keepassxc-browser/tests/html/div4.js b/tests/scripts/div4.js similarity index 86% rename from keepassxc-browser/tests/html/div4.js rename to tests/scripts/div4.js index db7db8f..30a1182 100644 --- a/keepassxc-browser/tests/html/div4.js +++ b/tests/scripts/div4.js @@ -1,11 +1,11 @@ 'use script'; -document.getElementById('toggle').addEventListener('click', function(e) { - const loginForm = document.getElementById('loginForm'); +document.getElementById('toggle4').addEventListener('click', function(e) { + const loginForm = document.getElementById('loginForm4'); if (!loginForm) { const dialog = document.createElement('div'); - dialog.setAttribute('id', 'loginForm'); + dialog.setAttribute('id', 'loginForm4'); dialog.style.position = 'fixed'; dialog.style.zIndex = '1002'; @@ -48,8 +48,6 @@ document.getElementById('toggle').addEventListener('click', function(e) { wrapper.append(innerDiv); dialog.append(wrapper); - document.body.appendChild(dialog); - } else { - document.body.removeChild(loginForm); + e.currentTarget.parentElement.appendChild(dialog); } }); diff --git a/tests/tests.html b/tests/tests.html index b37db26..3a9961d 100644 --- a/tests/tests.html +++ b/tests/tests.html @@ -12,8 +12,6 @@ - - @@ -34,6 +32,12 @@ + +
@@ -71,9 +75,132 @@
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + diff --git a/tests/tests.js b/tests/tests.js index 59b3458..304d6fb 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -61,25 +61,23 @@ async function testGeneral() { // Input field matching (keepassxc-browser.js) async function testInputFields() { - // Local filename, expected fields, action element ID (a button to be clicked) - const localFiles = [ - [ 'html/basic1.html', 2 ], // Username/passwd fields - [ 'html/basic2.html', 1 ], // Only username field - [ 'html/basic3.html', 1 ], // Only password field - [ 'html/basic4.html', 3 ], // Username/passwd/TOTP fields - [ 'html/div1.html', 2, '#toggle' ], // Fields are behind a button that must be pressed - [ 'html/div2.html', 2, '#toggle' ], // Fields are behind a button that must be pressed behind a JavaScript - [ 'html/div3.html', 2, '#toggle' ], // Fields are behind a button that must be pressed - [ 'html/div4.html', 2, '#toggle' ], // Fields are behind a button that must be pressed - [ 'html/hidden_fields1.html', 0 ], // Two hidden fields - [ 'html/hidden_fields2.html', 1 ], // Two hidden fields with one visible + // Div ID, expected fields, action element ID (a button to be clicked) + const testDivs = [ + [ 'basic1', 2 ], // Username/passwd fields + [ 'basic2', 1 ], // Only username field + [ 'basic3', 1 ], // Only password field + [ 'basic4', 3 ], // Username/passwd/TOTP fields + [ 'div1', 2, '#toggle1' ], // Fields are behind a button that must be pressed + [ 'div2', 2, '#toggle2' ], // Fields are behind a button that must be pressed behind a JavaScript + [ 'div3', 2, '#toggle3' ], // Fields are behind a button that must be pressed + [ 'div4', 2, '#toggle4' ], // Fields are behind a button that must be pressed + [ 'hiddenFields1', 0 ], // Two hidden fields + [ 'hiddenFields2', 1 ], // Two hidden fields with one visible ]; - for (const file of localFiles) { - await assertInputFields(file[0], file[1], file[2]); + for (const div of testDivs) { + await assertInputFields(div[0], div[1], div[2]); } - - document.getElementById('testFile').hidden = true; } // Search fields (kpxcFields @@ -140,20 +138,18 @@ async function testTotpFields() { // Password change async function testPasswordChange() { - // Local filename, expected new password - const localFiles = [ - [ 'html/passwordchange1.html', 'newPassword' ], // Default order without form - [ 'html/passwordchange2.html', 'newPassword' ], // Reversed order without form - [ 'html/passwordchange3.html', 'newPassword' ], // Default order with form - [ 'html/passwordchange4.html', 'newPassword' ], // Reversed order with form - [ 'html/passwordchange5.html', 'newPassword' ], // Each field has own form + // Div ID, expected new password + const localDivs = [ + [ 'passwordChange1', 'newPassword' ], // Default order without form + [ 'passwordChange2', 'newPassword' ], // Reversed order without form + [ 'passwordChange3', 'newPassword' ], // Default order with form + [ 'passwordChange4', 'newPassword' ], // Reversed order with form + [ 'passwordChange5', 'newPassword' ], // Each field has own form ]; - for (const file of localFiles) { - await assertPasswordChangeFields(file[0], file[1]); + for (const div of localDivs) { + await assertPasswordChangeFields(div[0], div[1]); } - - document.getElementById('testFile').hidden = true; } // Run tests