[go: up one dir, main page]

blob: c518c53dd22120394c00691720bc8b019b677ee0 [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/** @fileoverview Runs the Polymer Password View page tests. */
// clang-format off
import 'chrome://settings/lazy_load.js';
import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
import {PasswordViewElement} from 'chrome://settings/lazy_load.js';
import {buildRouter, PasswordManagerImpl, Router, routes} from 'chrome://settings/settings.js';
import {SettingsRoutes} from 'chrome://settings/settings_routes.js';
import {assertDeepEquals, assertEquals, assertFalse, assertTrue} from 'chrome://webui-test/chai_assert.js';
import {flushTasks, isVisible} from 'chrome://webui-test/test_util.js';
import {createMultiStorePasswordEntry, createPasswordEntry} from './passwords_and_autofill_fake_data.js';
import {TestPasswordManagerProxy} from './test_password_manager_proxy.js';
// clang-format on
function assertVisibilityOfPageElements(
page: PasswordViewElement, visible: boolean) {
['#usernameInput',
'#copyUsernameButton',
'#passwordInput',
'#showPasswordButton',
'#copyPasswordButton',
'#editButton',
'#deleteButton',
]
.forEach(
element => assertEquals(
visible, isVisible(page.shadowRoot!.querySelector(element))));
}
function assertVisibilityOfFederatedCredentialElements(
page: PasswordViewElement) {
['#usernameInput',
'#copyUsernameButton',
'#passwordInput',
'#deleteButton',
]
.forEach(
element =>
assertTrue(isVisible(page.shadowRoot!.querySelector(element))));
['#showPasswordButton',
'#copyPasswordButton',
'#editButton',
]
.forEach(
element =>
assertFalse(isVisible(page.shadowRoot!.querySelector(element))));
}
suite('PasswordViewTest', function() {
const SITE = 'site1.com';
const USERNAME = 'user1';
const USERNAME2 = 'user2';
const PASSWORD = 'p455w0rd';
const NOTE = 'some note';
const ID = 0;
let passwordManager: TestPasswordManagerProxy;
setup(function() {
loadTimeData.overrideValues(
{enablePasswordViewPage: true, enablePasswordNotes: false});
Router.resetInstanceForTesting(buildRouter());
routes.PASSWORD_VIEW =
(Router.getInstance().getRoutes() as SettingsRoutes).PASSWORD_VIEW;
document.body.innerHTML = '';
passwordManager = new TestPasswordManagerProxy();
PasswordManagerImpl.setInstance(passwordManager);
});
[{url: SITE, username: USERNAME, notesEnabled: false},
{url: SITE, username: '', notesEnabled: false},
{url: SITE, username: USERNAME, notesEnabled: true},
{url: SITE, username: '', notesEnabled: true}]
.forEach(
item => test(
`Valid site and username displays an entry ${item.notesEnabled}`,
async function() {
loadTimeData.overrideValues(
{enablePasswordNotes: item.notesEnabled});
const passwordList = [
createPasswordEntry({
url: item.url,
username: item.username,
id: ID,
note: NOTE,
}),
];
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: item.username,
site: item.url,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
assertVisibilityOfPageElements(page, /*visibility=*/ true);
if (item.notesEnabled) {
assertEquals(
NOTE,
page.shadowRoot!.querySelector(
'settings-textarea')!.value);
} else {
assertFalse(isVisible(
page.shadowRoot!.querySelector('settings-textarea')));
}
}));
[{inAccount: false, onDevice: true, username: USERNAME},
{inAccount: true, onDevice: false, username: USERNAME},
{inAccount: true, onDevice: true, username: USERNAME2},
]
.forEach(
item => test(
'inAccount and onDevice parameters are used for finding credentials',
async function() {
const passwordList = [
// entry in the account store
createPasswordEntry({
url: SITE,
username: USERNAME,
frontendId: 1,
id: 1,
fromAccountStore: true
}),
// entry in the device store
createPasswordEntry({
url: SITE,
username: USERNAME,
frontendId: 2,
id: 2,
fromAccountStore: false
}),
// entry in both stores are the next two items
createPasswordEntry({
url: SITE,
username: USERNAME2,
frontendId: 3,
id: 3,
fromAccountStore: false
}),
createPasswordEntry({
url: SITE,
username: USERNAME2,
frontendId: 3,
id: 4,
fromAccountStore: true
}),
];
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: item.username,
site: SITE,
});
if (item.inAccount) {
params.set('inAccount', 'true');
if (item.onDevice) {
params.set('onDevice', 'true');
}
}
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
assertVisibilityOfPageElements(page, /*visibility=*/ true);
assertEquals(
item.inAccount, page.credential!.isPresentInAccount());
assertEquals(
item.onDevice, page.credential!.isPresentOnDevice());
assertEquals(item.username, page.credential!.username);
}));
test('Empty note shows placeholder text', async function() {
loadTimeData.overrideValues({enablePasswordNotes: true});
const passwordList = [
createPasswordEntry({url: SITE, username: USERNAME, id: ID}),
];
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
assertEquals(
'No note added',
page.shadowRoot!.querySelector('settings-textarea')!.value);
});
test(
'Invalid site and username does not display an entry ' +
'and routes to passwords page',
async function() {
const passwordList = [
createPasswordEntry({url: SITE, username: 'user2', id: ID}),
];
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
assertVisibilityOfPageElements(page, /*visibility=*/ false);
assertEquals(routes.PASSWORDS, Router.getInstance().getCurrentRoute());
});
test('Federated credential layout', async function() {
const passwordList = [
createPasswordEntry({
federationText: 'with chromium.org',
url: SITE,
username: USERNAME,
}),
];
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
assertVisibilityOfFederatedCredentialElements(page);
});
test('Clicking show password button shows / hides it', async function() {
const passwordList = [
createPasswordEntry({url: SITE, username: USERNAME, id: ID}),
];
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
const passwordInput =
page.shadowRoot!.querySelector<HTMLInputElement>('#passwordInput');
const showButton = page.shadowRoot!.querySelector<HTMLButtonElement>(
'#showPasswordButton');
assertTrue(!!passwordInput);
assertTrue(!!showButton);
assertEquals('password', passwordInput.type);
assertEquals(' '.repeat(10), passwordInput.value);
assertTrue(showButton.classList.contains('icon-visibility'));
showButton.click();
// this will fail because setPlaintextPassword is not called.
await passwordManager.whenCalled('requestPlaintextPassword');
await flushTasks();
assertEquals('password', passwordInput.type);
assertEquals(' '.repeat(10), passwordInput.value);
assertTrue(showButton.classList.contains('icon-visibility'));
passwordManager.setPlaintextPassword(PASSWORD);
// show the password
showButton.click();
const {id, reason} =
await passwordManager.whenCalled('requestPlaintextPassword');
await flushTasks();
assertEquals(ID, id);
assertEquals(chrome.passwordsPrivate.PlaintextReason.VIEW, reason);
assertEquals('text', passwordInput.type);
assertEquals(PASSWORD, passwordInput.value);
assertTrue(showButton.classList.contains('icon-visibility-off'));
// hide the password by re-clicking
showButton.click();
flush();
assertEquals('password', passwordInput.type);
assertEquals(' '.repeat(10), passwordInput.value);
assertTrue(showButton.classList.contains('icon-visibility'));
});
test(
'When saved passwords change credential is still shown',
async function() {
const passwordEntry =
createPasswordEntry({url: SITE, username: USERNAME, id: ID});
passwordManager.data.passwords = [passwordEntry];
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
assertTrue(!!page.credential);
assertEquals(SITE, page.credential.urls.shown);
assertEquals(USERNAME, page.credential.username);
passwordManager.lastCallback.addSavedPasswordListChangedListener!
([passwordEntry].concat([
createPasswordEntry({url: 'site2.com', username: 'user2', id: 1}),
]));
await flushTasks();
assertTrue(!!page.credential);
assertEquals(SITE, page.credential.urls.shown);
assertEquals(USERNAME, page.credential.username);
});
test(
'When saved passwords change and credential is removed, page is empty',
async function() {
const passwordList = [
createPasswordEntry({url: SITE, username: USERNAME, id: ID}),
];
passwordManager.setPlaintextPassword(PASSWORD);
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
assertVisibilityOfPageElements(page, /*visibility=*/ true);
passwordManager.lastCallback.addSavedPasswordListChangedListener!([]);
await flushTasks();
assertVisibilityOfPageElements(page, /*visibility=*/ false);
});
test(
'When edit button is tapped, the edit dialog is open with credential. ' +
'When the username is changed, view page gets updated',
async function() {
const NEW_USERNAME = 'user2';
const entry =
createPasswordEntry({url: SITE, username: USERNAME, id: 0});
passwordManager.setPlaintextPassword(PASSWORD);
passwordManager.data.passwords = [entry];
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
page.shadowRoot!.querySelector<HTMLButtonElement>(
'#editButton')!.click();
flush();
await passwordManager.whenCalled('requestPlaintextPassword');
await flushTasks();
const editDialog =
page.shadowRoot!.querySelector('password-edit-dialog');
assertTrue(!!editDialog);
assertTrue(!!editDialog.existingEntry);
assertEquals(entry.urls, editDialog.existingEntry.urls);
assertEquals(entry.username, editDialog.existingEntry.username);
assertEquals(PASSWORD, editDialog.existingEntry.password);
// user edits the username
editDialog.$.usernameInput.value = NEW_USERNAME;
editDialog.$.actionButton.click();
entry.username = NEW_USERNAME;
passwordManager.lastCallback.addSavedPasswordListChangedListener!
([entry]);
await flushTasks();
assertFalse(isVisible(editDialog));
assertEquals(NEW_USERNAME, page.credential!.username);
const urlParams = Router.getInstance().getQueryParameters();
assertEquals(urlParams.get('site'), SITE);
assertEquals(urlParams.get('username'), NEW_USERNAME);
assertEquals(
routes.PASSWORD_VIEW, Router.getInstance().getCurrentRoute());
});
test(
'When delete button is clicked for a password on device, ' +
'it is deleted and routed to passwords page',
async function() {
const entry = createPasswordEntry(
{url: SITE, username: USERNAME, id: ID, fromAccountStore: false});
passwordManager.setPlaintextPassword(PASSWORD);
passwordManager.data.passwords = [entry];
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
page.shadowRoot!.querySelector<HTMLButtonElement>(
'#deleteButton')!.click();
const id = await passwordManager.whenCalled('removeSavedPassword');
assertEquals(ID, id);
await flushTasks();
assertEquals(routes.PASSWORDS, Router.getInstance().getCurrentRoute());
const newParams = Router.getInstance().getQueryParameters();
assertEquals('false', newParams.get('removedFromAccount'));
assertEquals('true', newParams.get('removedFromDevice'));
});
test(
'When delete button is clicked for a password on device and account, ' +
'remove dialog is opened',
async function() {
passwordManager.setPlaintextPassword(PASSWORD);
passwordManager.data.passwords = [
createPasswordEntry({
url: SITE,
username: USERNAME,
id: 0,
frontendId: ID,
fromAccountStore: false
}),
createPasswordEntry({
url: SITE,
username: USERNAME,
id: 1,
frontendId: ID,
fromAccountStore: true
})
];
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
inAccount: 'true',
onDevice: 'true',
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
page.shadowRoot!.querySelector<HTMLButtonElement>(
'#deleteButton')!.click();
flush();
const dialog = page.shadowRoot!.querySelector('password-remove-dialog');
assertTrue(!!dialog);
assertDeepEquals(
createMultiStorePasswordEntry(
{url: SITE, username: USERNAME, deviceId: 0, accountId: 1}),
dialog.duplicatedPassword);
// click delete on the dialog.
dialog.$.removeButton.click();
await passwordManager.whenCalled('removeSavedPasswords');
await flushTasks();
assertEquals(
routes.PASSWORDS.path, Router.getInstance().getCurrentRoute().path);
const pageParams = Router.getInstance().getQueryParameters();
assertEquals('true', pageParams.get('removedFromAccount'));
assertEquals('true', pageParams.get('removedFromDevice'));
});
test('Copy password button shows the copy toast', async function() {
const passwordList = [
createPasswordEntry({url: SITE, username: USERNAME, id: ID}),
];
passwordManager.setPlaintextPassword(PASSWORD);
passwordManager.data.passwords = passwordList;
const page = document.createElement('password-view');
document.body.appendChild(page);
const params = new URLSearchParams({
username: USERNAME,
site: SITE,
});
Router.getInstance().navigateTo(routes.PASSWORD_VIEW, params);
await flushTasks();
const copyButton = page.shadowRoot!.querySelector<HTMLButtonElement>(
'#copyPasswordButton')!;
assertFalse(page.$.toast.open);
copyButton.click();
await passwordManager.whenCalled('requestPlaintextPassword');
await flushTasks();
assertTrue(page.$.toast.open);
});
});