mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
First draft
This commit is contained in:
parent
2c5220eea4
commit
c18cbc630d
41 changed files with 5632 additions and 3618 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -10,3 +10,5 @@
|
|||
.bz2
|
||||
.xz
|
||||
node_modules/*
|
||||
test-results/
|
||||
playwright-report/
|
||||
|
|
|
|||
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node-terminal",
|
||||
"name": "Run Script: Playwright tests",
|
||||
"request": "launch",
|
||||
"command": "npx playwright test",
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
927
keepassxc-browser/common/browser-polyfill.min.js
vendored
927
keepassxc-browser/common/browser-polyfill.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -787,59 +787,63 @@ if (document.readyState === 'complete' || (document.readyState !== 'loading' &&
|
|||
document.addEventListener('DOMContentLoaded', initContentScript);
|
||||
}
|
||||
|
||||
// These are executed in each frame
|
||||
browser.runtime.onMessage.addListener(async function(req, sender) {
|
||||
if ('action' in req) {
|
||||
// Don't allow any actions if the site is ignored
|
||||
if (await kpxc.siteIgnored()) {
|
||||
logDebug('This site is ignored in Site Preferences.');
|
||||
return;
|
||||
}
|
||||
// These are executed in each frame. Try/catch is for tests where browser can be undefined.
|
||||
try {
|
||||
browser.runtime.onMessage.addListener(async function(req, sender) {
|
||||
if ('action' in req) {
|
||||
// Don't allow any actions if the site is ignored
|
||||
if (await kpxc.siteIgnored()) {
|
||||
logDebug('This site is ignored in Site Preferences.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (req.action === 'activated_tab') {
|
||||
kpxc.triggerActivatedTab();
|
||||
} else if (req.action === 'add_username_only_option') {
|
||||
kpxc.addToSitePreferences();
|
||||
} else if (req.action === 'check_database_hash' && 'hash' in req) {
|
||||
kpxc.detectDatabaseChange(req);
|
||||
} else if (req.action === 'choose_credential_fields') {
|
||||
kpxcDefine.init();
|
||||
} else if (req.action === 'clear_credentials') {
|
||||
kpxc.clearAllFromPage();
|
||||
} else if (req.action === 'fill_user_pass_with_specific_login') {
|
||||
kpxcFill.fillFromPopup(req.id, req.uuid);
|
||||
} else if (req.action === 'fill_username_password') {
|
||||
sendMessage('page_set_manual_fill', ManualFill.BOTH);
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillInFromActiveElement();
|
||||
} else if (req.action === 'fill_password') {
|
||||
sendMessage('page_set_manual_fill', ManualFill.PASSWORD);
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillInFromActiveElement(true); // passOnly to true
|
||||
} else if (req.action === 'fill_totp') {
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillFromTOTP();
|
||||
} else if (req.action === 'fill_attribute' && req.args) {
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillAttributeToActiveElementWith(req.args);
|
||||
} else if (req.action === 'ignore_site') {
|
||||
kpxc.ignoreSite(req.args);
|
||||
} else if (req.action === 'redetect_fields') {
|
||||
const response = await sendMessage('load_settings');
|
||||
kpxc.settings = response;
|
||||
kpxc.inputs = [];
|
||||
kpxc.combinations = [];
|
||||
kpxc.initCredentialFields();
|
||||
} else if (req.action === 'reload_extension') {
|
||||
sendMessage('reconnect');
|
||||
} else if (req.action === 'save_credentials') {
|
||||
kpxc.rememberCredentialsFromContextMenu();
|
||||
} else if (req.action === 'retrive_credentials_forced') {
|
||||
await kpxc.retrieveCredentials(true);
|
||||
} else if (req.action === 'show_password_generator') {
|
||||
kpxcPasswordDialog.trigger();
|
||||
} else if (req.action === 'request_autotype') {
|
||||
sendMessage('request_autotype', [ window.location.hostname ]);
|
||||
if (req.action === 'activated_tab') {
|
||||
kpxc.triggerActivatedTab();
|
||||
} else if (req.action === 'add_username_only_option') {
|
||||
kpxc.addToSitePreferences();
|
||||
} else if (req.action === 'check_database_hash' && 'hash' in req) {
|
||||
kpxc.detectDatabaseChange(req);
|
||||
} else if (req.action === 'choose_credential_fields') {
|
||||
kpxcDefine.init();
|
||||
} else if (req.action === 'clear_credentials') {
|
||||
kpxc.clearAllFromPage();
|
||||
} else if (req.action === 'fill_user_pass_with_specific_login') {
|
||||
kpxcFill.fillFromPopup(req.id, req.uuid);
|
||||
} else if (req.action === 'fill_username_password') {
|
||||
sendMessage('page_set_manual_fill', ManualFill.BOTH);
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillInFromActiveElement();
|
||||
} else if (req.action === 'fill_password') {
|
||||
sendMessage('page_set_manual_fill', ManualFill.PASSWORD);
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillInFromActiveElement(true); // passOnly to true
|
||||
} else if (req.action === 'fill_totp') {
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillFromTOTP();
|
||||
} else if (req.action === 'fill_attribute' && req.args) {
|
||||
await kpxc.receiveCredentialsIfNecessary();
|
||||
kpxcFill.fillAttributeToActiveElementWith(req.args);
|
||||
} else if (req.action === 'ignore_site') {
|
||||
kpxc.ignoreSite(req.args);
|
||||
} else if (req.action === 'redetect_fields') {
|
||||
const response = await sendMessage('load_settings');
|
||||
kpxc.settings = response;
|
||||
kpxc.inputs = [];
|
||||
kpxc.combinations = [];
|
||||
kpxc.initCredentialFields();
|
||||
} else if (req.action === 'reload_extension') {
|
||||
sendMessage('reconnect');
|
||||
} else if (req.action === 'save_credentials') {
|
||||
kpxc.rememberCredentialsFromContextMenu();
|
||||
} else if (req.action === 'retrive_credentials_forced') {
|
||||
await kpxc.retrieveCredentials(true);
|
||||
} else if (req.action === 'show_password_generator') {
|
||||
kpxcPasswordDialog.trigger();
|
||||
} else if (req.action === 'request_autotype') {
|
||||
sendMessage('request_autotype', [ window.location.hostname ]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e.message);
|
||||
}
|
||||
|
|
|
|||
110
keepassxc-browser/tests/assert.js
Normal file
110
keepassxc-browser/tests/assert.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
'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);
|
||||
}
|
||||
6
keepassxc-browser/tests/basic1.html
Normal file
6
keepassxc-browser/tests/basic1.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<input placeholder="username" type="text" name="loginField">
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
</body>
|
||||
</html>
|
||||
75
keepassxc-browser/tests/example.spec.js
Normal file
75
keepassxc-browser/tests/example.spec.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
'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');
|
||||
};
|
||||
9
keepassxc-browser/tests/global-setup.js
Normal file
9
keepassxc-browser/tests/global-setup.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
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);
|
||||
};
|
||||
8
keepassxc-browser/tests/global-teardown.js
Normal file
8
keepassxc-browser/tests/global-teardown.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
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);
|
||||
};
|
||||
6
keepassxc-browser/tests/html/basic1.html
Normal file
6
keepassxc-browser/tests/html/basic1.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<html>
|
||||
<body>
|
||||
<input placeholder="username" type="text" name="loginField">
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
</body>
|
||||
</html>
|
||||
5
keepassxc-browser/tests/html/basic2.html
Normal file
5
keepassxc-browser/tests/html/basic2.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<input placeholder="username" type="text" name="loginField">
|
||||
</body>
|
||||
</html>
|
||||
5
keepassxc-browser/tests/html/basic3.html
Normal file
5
keepassxc-browser/tests/html/basic3.html
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<html>
|
||||
<body>
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
</body>
|
||||
</html>
|
||||
7
keepassxc-browser/tests/html/basic4.html
Normal file
7
keepassxc-browser/tests/html/basic4.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
<input placeholder="username" type="text" name="loginField">
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
<input type="text" id="auth">
|
||||
</body>
|
||||
</html>
|
||||
2
keepassxc-browser/tests/html/basictest.html
Normal file
2
keepassxc-browser/tests/html/basictest.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<input placeholder="username" type="text" name="loginField">
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
14
keepassxc-browser/tests/html/div1.html
Normal file
14
keepassxc-browser/tests/html/div1.html
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<html>
|
||||
<head>
|
||||
<script defer src="div1.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button id="toggle">View login form</button>
|
||||
<div id="loginForm" style="display: none;">
|
||||
<input placeholder="username" type="text" name="loginField">
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
11
keepassxc-browser/tests/html/div1.js
Normal file
11
keepassxc-browser/tests/html/div1.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
'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';
|
||||
}
|
||||
});
|
||||
21
keepassxc-browser/tests/html/div2.html
Normal file
21
keepassxc-browser/tests/html/div2.html
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<html>
|
||||
<head>
|
||||
<script defer src="div2.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button id="toggle">View login form</button>
|
||||
|
||||
<div id="dialog" style="position: relative; z-index: auto;">
|
||||
<div id="outer" style="overflow: hidden; height: 0px;">
|
||||
<div id="inner" style="margin: -197px auto auto;">
|
||||
<form method="post" class="signin" action="#">
|
||||
<input placeholder="username" type="text" name="loginField">
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
18
keepassxc-browser/tests/html/div2.js
Normal file
18
keepassxc-browser/tests/html/div2.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
'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';
|
||||
}
|
||||
});
|
||||
10
keepassxc-browser/tests/html/div3.html
Normal file
10
keepassxc-browser/tests/html/div3.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<script defer src="div3.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button id="toggle">View login form</button>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
26
keepassxc-browser/tests/html/div3.js
Normal file
26
keepassxc-browser/tests/html/div3.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
'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);
|
||||
}
|
||||
});
|
||||
10
keepassxc-browser/tests/html/div4.html
Normal file
10
keepassxc-browser/tests/html/div4.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<head>
|
||||
<script defer src="div4.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<button id="toggle">View login form</button>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
55
keepassxc-browser/tests/html/div4.js
Normal file
55
keepassxc-browser/tests/html/div4.js
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
'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);
|
||||
}
|
||||
});
|
||||
12
keepassxc-browser/tests/html/hidden_fields1.html
Normal file
12
keepassxc-browser/tests/html/hidden_fields1.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<body>
|
||||
<div style="margin-left: -500px;">
|
||||
<input placeholder="outsideLeft" type="password" name="outsideLeft">
|
||||
</div>
|
||||
|
||||
<div style="margin-top: -500px;">
|
||||
<input placeholder="outsideTop" type="password" name="outsideTop">
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
18
keepassxc-browser/tests/html/hidden_fields2.html
Normal file
18
keepassxc-browser/tests/html/hidden_fields2.html
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<html>
|
||||
<style>
|
||||
.hiddenOne {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
<body>
|
||||
<div>
|
||||
<input placeholder="zeroSize" type="password" name="zeroSize" style="width: 0px; height: 0px;">
|
||||
<input placeholder="oneSize" type="password" name="oneSize" style="width: 1px; height: 1px;">
|
||||
<input placeholder="visibilityHidden" type="password" name="visibilityHidden" style="visibility: hidden;">
|
||||
<input placeholder="visibilityCollapse" type="password" name="visibilityCollapse" style="visibility: collapse;">
|
||||
<input placeholder="displayNone" type="password" name="displayNone" style="display: none;">
|
||||
<input placeholder="hiddenOne" type="password" name="hiddenOne" class="hiddenOne">
|
||||
<input placeholder="normal" type="password" name="normal">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
7
keepassxc-browser/tests/html/passwordchange1.html
Normal file
7
keepassxc-browser/tests/html/passwordchange1.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
<br /><input type="password" name="Old password" value="oldPassword">
|
||||
<br /><input type="password" name="New password" value="newPassword">
|
||||
<br /><input type="password" name="Repeat password" value="newPassword">
|
||||
</body>
|
||||
</html>
|
||||
7
keepassxc-browser/tests/html/passwordchange2.html
Normal file
7
keepassxc-browser/tests/html/passwordchange2.html
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<html>
|
||||
<body>
|
||||
<br /><input type="password" name="New password" value="newPassword">
|
||||
<br /><input type="password" name="Repeat password" value="newPassword">
|
||||
<br /><input type="password" name="Old password" value="oldPassword">
|
||||
</body>
|
||||
</html>
|
||||
10
keepassxc-browser/tests/html/passwordchange3.html
Normal file
10
keepassxc-browser/tests/html/passwordchange3.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<body>
|
||||
<form action="secondPage">
|
||||
<br /><input type="password" name="Old password" value="oldPassword">
|
||||
<br /><input type="password" name="New password" value="newPassword">
|
||||
<br /><input type="password" name="Repeat password" value="newPassword">
|
||||
<br /><input type="submit" value="Change password">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
10
keepassxc-browser/tests/html/passwordchange4.html
Normal file
10
keepassxc-browser/tests/html/passwordchange4.html
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<html>
|
||||
<body>
|
||||
<form action="secondPage">
|
||||
<br /><input type="password" name="New password" value="newPassword">
|
||||
<br /><input type="password" name="Repeat password" value="newPassword">
|
||||
<br /><input type="password" name="Old password" value="oldPassword">
|
||||
<br /><input type="submit" value="Change password">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
||||
17
keepassxc-browser/tests/html/passwordchange5.html
Normal file
17
keepassxc-browser/tests/html/passwordchange5.html
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<html>
|
||||
<body>
|
||||
|
||||
<form action="firstForm">
|
||||
<br /><input type="password" name="Old password" value="oldPassword">
|
||||
</form>
|
||||
|
||||
<form action="secondForm">
|
||||
<br /><input type="password" name="New password" value="newPassword">
|
||||
</form>
|
||||
|
||||
<form action="thirdForm">
|
||||
<br /><input type="password" name="Repeat password" value="newPassword">
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
79
keepassxc-browser/tests/tests.html
Normal file
79
keepassxc-browser/tests/tests.html
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title data-i18n="popupTitle"></title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<link rel="stylesheet" href="../css/colors.css" />
|
||||
<link rel="stylesheet" href="../bootstrap/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="../fonts/fork-awesome.min.css" />
|
||||
<link rel="stylesheet" href="../options/options.css" />
|
||||
<link rel="icon" type="image/png" href="../icons/keepassxc_32x32.png" sizes="32x32">
|
||||
<link rel="icon" type="image/png" href="../icons/keepassxc_64x64.png" sizes="64x64">
|
||||
<link rel="icon" type="image/png" href="../icons/keepassxc_96x96.png" sizes="96x96">
|
||||
<script src="../common/browser-polyfill.min.js"></script>
|
||||
<script src="../bootstrap/jquery-3.4.1.min.js"></script>
|
||||
<script src="../bootstrap/bootstrap.min.js"></script>
|
||||
<script defer src="../common/global.js"></script>
|
||||
<script defer src="../common/sites.js"></script>
|
||||
<script defer src="../content/ui.js"></script>
|
||||
<script defer src="../content/pwgen.js"></script>
|
||||
<script defer src="../content/define.js"></script>
|
||||
<script defer src="../content/autocomplete.js"></script>
|
||||
<script defer src="../content/banner.js"></script>
|
||||
<script defer src="../content/credential-autocomplete.js"></script>
|
||||
<script defer src="../content/fields.js"></script>
|
||||
<script defer src="../content/form.js"></script>
|
||||
<script defer src="../content/fill.js"></script>
|
||||
<script defer src="../content/keepassxc-browser.js"></script>-->
|
||||
<script defer src="../content/observer-helper.js"></script>
|
||||
<script defer src="../content/totp-autocomplete.js"></script>
|
||||
<script defer src="../content/totp-field.js"></script>
|
||||
<script defer src="../content/username-field.js"></script>
|
||||
<script defer src="assert.js"></script>
|
||||
<script defer src="tests.js"></script>
|
||||
</head>
|
||||
|
||||
<body class="pt-3 pb-5">
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<main class="col-md-9 col-lg-10 offset-md-3 offset-lg-2 px-4 mt-5 mt-md-0">
|
||||
<div class="content">
|
||||
|
||||
<!-- Content script tests -->
|
||||
<div class="tab">
|
||||
<h2 class="pb-3 mt-0">Content script tests</h2>
|
||||
|
||||
<div class="card my-4 shadow">
|
||||
<div class="card-header h6 rounded-0"><i class="fa fa-meh-o" aria-hidden="true"></i> General (global.js)</div>
|
||||
<div class="card-body" id="general-results"></div>
|
||||
</div>
|
||||
|
||||
<div class="card my-4 shadow">
|
||||
<div class="card-header h6 rounded-0"><i class="fa fa-meh-o" aria-hidden="true"></i> Input field matching (keepassxc-browser.js)</div>
|
||||
<div class="card-body" id="input-field-results"></div>
|
||||
</div>
|
||||
|
||||
<div class="card my-4 shadow">
|
||||
<div class="card-header h6 rounded-0"><i class="fa fa-meh-o" aria-hidden="true"></i> Search fields (keepassxc-browser.js)</div>
|
||||
<div class="card-body" id="search-field-results"></div>
|
||||
</div>
|
||||
|
||||
<div class="card my-4 shadow">
|
||||
<div class="card-header h6 rounded-0"><i class="fa fa-meh-o" aria-hidden="true"></i> TOTP fields (totp-field.js)</div>
|
||||
<div class="card-body" id="totp-field-results"></div>
|
||||
</div>
|
||||
|
||||
<div class="card my-4 shadow">
|
||||
<div class="card-header h6 rounded-0"><i class="fa fa-meh-o" aria-hidden="true"></i> Password change (keepassxc-browser.js)</div>
|
||||
<div class="card-body" id="password-change-results"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<iframe id="testFile" width="100%" height="600" frameborder="0"></iframe>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
168
keepassxc-browser/tests/tests.js
Normal file
168
keepassxc-browser/tests/tests.js
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
'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(),
|
||||
]);
|
||||
})();
|
||||
7254
package-lock.json
generated
7254
package-lock.json
generated
File diff suppressed because it is too large
Load diff
12
package.json
12
package.json
|
|
@ -4,21 +4,19 @@
|
|||
"description": "KeePassXC-Browser",
|
||||
"main": "build.js",
|
||||
"devDependencies": {
|
||||
"eslint": "^6.8.0",
|
||||
"@playwright/test": "^1.20.0",
|
||||
"eslint": "^8.11.0",
|
||||
"eslint-config-airbnb-base": "^14.2.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"mocha": "^8.4.0",
|
||||
"zip-a-folder": "0.0.12"
|
||||
"eslint-plugin-import": "^2.24.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"file-url": "^3.0.0",
|
||||
"fs-extra": "^8.1.0",
|
||||
"selenium-webdriver": "^3.6.0",
|
||||
"soft-assert": "^0.2.6"
|
||||
"zip-a-folder": "0.0.12"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "node build.js",
|
||||
"tests": "./node_modules/mocha/bin/mocha run_tests.js"
|
||||
"tests": "npx playwright test"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
35
playwright.config.js
Normal file
35
playwright.config.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
const { devices } = require('@playwright/test');
|
||||
|
||||
const config = {
|
||||
testDir: './tests',
|
||||
timeout: 30 * 1000,
|
||||
expect: {
|
||||
timeout: 5000
|
||||
},
|
||||
forbidOnly: !!process.env.CI,
|
||||
globalSetup: require.resolve('./tests/global-setup'),
|
||||
//globalTeardown: require.resolve('./tests/global-teardown'),
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
reporter: 'list',
|
||||
use: {
|
||||
actionTimeout: 0,
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
...devices['Desktop Chrome'],
|
||||
},
|
||||
},
|
||||
/*{
|
||||
name: 'firefox',
|
||||
use: {
|
||||
...devices['Desktop Firefox'],
|
||||
},
|
||||
},*/
|
||||
],
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
65
run_tests.js
65
run_tests.js
|
|
@ -1,65 +0,0 @@
|
|||
const firefox = require('selenium-webdriver/firefox'),
|
||||
webdriver = require('selenium-webdriver'),
|
||||
By = require('selenium-webdriver').By,
|
||||
test = require('selenium-webdriver/testing'),
|
||||
assert = require('selenium-webdriver/testing/assert'),
|
||||
fileUrl = require('file-url'),
|
||||
softAssert = require('soft-assert'),
|
||||
fs = require('fs-extra');
|
||||
|
||||
const DEST = 'keepassxc-browser/tests';
|
||||
let browser;
|
||||
|
||||
test.before(async function(done) {
|
||||
// Create a temporary directory and copy tests/* to keepassxc-browser/tests
|
||||
await fs.ensureDir(DEST);
|
||||
await fs.copy('./tests', DEST);
|
||||
|
||||
const options = new firefox.Options();
|
||||
options.addArguments('--headless');
|
||||
|
||||
browser = await new webdriver.Builder().forBrowser('firefox').setFirefoxOptions(options).build();
|
||||
browser.get(fileUrl(`${DEST}/tests.html`));
|
||||
done();
|
||||
});
|
||||
|
||||
test.after(async function() {
|
||||
softAssert.softAssertAll();
|
||||
browser.quit();
|
||||
|
||||
// Delete previously created temporary directory. Comment for re-running tests manually inside the extension.
|
||||
await fs.remove(DEST);
|
||||
});
|
||||
|
||||
test.describe('Content script tests', function() {
|
||||
test.it('General tests', function() {
|
||||
test.verifyResults('#general-results .fa');
|
||||
});
|
||||
|
||||
test.it('Input field matching tests', function() {
|
||||
test.verifyResults('#input-field-results .fa');
|
||||
});
|
||||
|
||||
test.it('Search field tests', function() {
|
||||
test.verifyResults('#search-field-results .fa');
|
||||
});
|
||||
|
||||
test.it('TOTP field tests', function() {
|
||||
test.verifyResults('#totp-field-results .fa');
|
||||
});
|
||||
|
||||
test.it('Password change tests', function() {
|
||||
test.verifyResults('#password-change-results .fa');
|
||||
});
|
||||
|
||||
test.verifyResults = function(selector) {
|
||||
browser.findElements(By.css(selector)).then(elems => {
|
||||
elems.forEach(e => {
|
||||
e.getAttribute('class').then(async c => {
|
||||
const next = await e.findElements(By.xpath('./following::span'));
|
||||
assert(c).contains('fa-check', await next[0].getText());
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
75
tests/example.spec.js
Normal file
75
tests/example.spec.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
'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');
|
||||
};
|
||||
9
tests/global-setup.js
Normal file
9
tests/global-setup.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
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);
|
||||
};
|
||||
8
tests/global-teardown.js
Normal file
8
tests/global-teardown.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
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);
|
||||
};
|
||||
2
tests/html/basictest.html
Normal file
2
tests/html/basictest.html
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
<input placeholder="username" type="text" name="loginField">
|
||||
<input placeholder="password" type="password" name="passwordField">
|
||||
|
|
@ -15,16 +15,20 @@
|
|||
<script src="../bootstrap/jquery-3.4.1.min.js"></script>
|
||||
<script src="../bootstrap/bootstrap.min.js"></script>
|
||||
<script defer src="../common/global.js"></script>
|
||||
<script defer src="../common/translate.js"></script>
|
||||
<script defer src="../common/sites.js"></script>
|
||||
<script defer src="../content/ui.js"></script>
|
||||
<script defer src="../content/pwgen.js"></script>
|
||||
<script defer src="../content/define.js"></script>
|
||||
<script defer src="../content/autocomplete.js"></script>
|
||||
<script defer src="../content/banner.js"></script>
|
||||
<script defer src="../content/sites.js"></script>
|
||||
<script defer src="../content/credential-autocomplete.js"></script>
|
||||
<script defer src="../content/fields.js"></script>
|
||||
<script defer src="../content/form.js"></script>
|
||||
<script defer src="../content/fill.js"></script>
|
||||
<script defer src="../content/keepassxc-browser.js"></script>-->
|
||||
<script defer src="../content/observer-helper.js"></script>
|
||||
<script defer src="../content/totp-autocomplete.js"></script>
|
||||
<script defer src="../content/totp-field.js"></script>
|
||||
<script defer src="../content/keepassxc-browser.js"></script>
|
||||
<script defer src="../content/username-field.js"></script>
|
||||
<script defer src="assert.js"></script>
|
||||
<script defer src="tests.js"></script>
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const Tests = {
|
|||
};
|
||||
|
||||
function createResult(card, res, text) {
|
||||
const icon = kpxcUI.createElement('i', res ? 'fa fa-check' : 'fa fa-close');
|
||||
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');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue