Commit 7630206b authored by David Burke's avatar David Burke Committed by Brendan Berkley

Resolve "e2e is broken"

parent aec6db98
......@@ -26,7 +26,6 @@ testem.log
/typings
# e2e
/e2e/*.js
/e2e/*.map
#System Files
......
image: trion/ng-cli-karma
image: registry.gitlab.com/dasch8/angular-ci:latest
variables:
DOCKER_DRIVER: overlay2
......@@ -18,23 +18,7 @@ test:
script:
- yarn install
- yarn run lint
- yarn run test --progress false --single-run=true --watch=false
e2e-firefox:
stage: test
image: registry.gitlab.com/passit/docker-compose
services:
- docker:dind
script:
- $COMPOSE build web
- $COMPOSE up -d web selenium-firefox
- sleep 15
- $COMPOSE run --no-deps -e CI_JOB_ID=$CI_JOB_ID -e CI_COMMIT_REF_NAME=$CI_COMMIT_REF_NAME -e CI_COMMIT_SHA=$CI_COMMIT_SHA -e CI_COMMIT_TAG=$CI_COMMIT_TAG -e CI_PROJECT_PATH=$CI_PROJECT_PATH -e FIREFOX=true web e2e:docker
artifacts:
when: on_failure
expire_in: 2 weeks
paths:
- reports/
- yarn run test --progress false --single-run=true --watch=false --browsers=Chromium_CI
build-web-assets:
stage: test
......
......@@ -168,15 +168,20 @@
}
},
"passit-e2e": {
"root": "",
"root": "e2e/",
"sourceRoot": "",
"projectType": "application",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "./protractor.conf.js",
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "passit:serve"
},
"configurations": {
"production": {
"devServerTarget": "passit:serve:production"
}
}
},
"lint": {
......
version: '2'
services:
selenium-chrome:
image: selenium/standalone-chrome:3.8
volumes:
- /dev/shm:/dev/shm
# so it can load extension
- .:/dist
selenium-firefox:
image: selenium/standalone-firefox:3.8
volumes:
- /dev/shm:/dev/shm
web:
image: registry.gitlab.com/dasch8/angular-ci:latest
......@@ -19,7 +19,7 @@ services:
environment:
DATABASE_URL: postgres://postgres:postgres@db:5432/postgres
IS_TEST_MODE: 'True'
IS_PRIVATE_ORG_MODE: "False"
IS_PRIVATE_ORG_MODE: "True"
IS_DEBUG: "True"
ports:
- "8000:8000"
......
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require("jasmine-spec-reporter");
const { DOCKER, FIREFOX, EXTENSION } = process.env;
exports.config = {
allScriptsTimeout: 21000, // odd number makes it easier to identify
specs: ["./src/**/*.e2e-spec.ts"],
capabilities: FIREFOX
? {
browserName: "firefox"
}
: {
browserName: "chrome"
},
directConnect: true,
framework: "jasmine",
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 41000,
print: function() {}
},
onPrepare() {
require("ts-node").register({
project: require("path").join(__dirname, "./tsconfig.e2e.json")
});
jasmine
.getEnv()
.addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};
if (process.env.IS_CI) {
exports.config.capabilities.chromeOptions = {
args: ["no-sandbox", "headless", "disable-gpu"]
};
}
......@@ -48,6 +48,12 @@ describe("Login and Register", () => {
await login(browser, USERNAME, PASSWORD);
await accountPage.navigateTo();
await accountPage.resetPasswordButton().click();
const changeBtn = accountPage.changePassBtn();
await EC.browser.wait(
EC.presenceOf(changeBtn),
100,
"Submit button doesn't exist"
);
});
it("shows an error if passwords don't match", async () => {
......@@ -89,7 +95,7 @@ describe("Login and Register", () => {
it("logged out and on login page", async () => {
const currentUrl = await browser.getCurrentUrl();
expect<any>(currentUrl).toEqual(browser.baseUrl + "login");
expect(currentUrl.endsWith("login")).toBeTruthy();
});
it("should login with new password", async () => {
......
......@@ -24,7 +24,7 @@ export class AppPage {
export class LoginPage {
navigateTo = async (testBrowser: ProtractorBrowser) =>
await testBrowser.get(testBrowser.baseUrl + "login");
await testBrowser.get("/login");
getParagraphText = async (testBrowser: ProtractorBrowser) =>
await testBrowser.element(by.className("heading-medium")).getText();
getEmailInput = () => element(by.id("Login Form.email"));
......@@ -66,28 +66,28 @@ export class LoginPage {
export class RegisterPage {
navigateTo = async () => await browser.get(browser.baseUrl + "/register");
clickEmailNextButton = async () =>
await element(by.id("email-button")).click();
clickPasswordNextButton = async () =>
await element(by.id("password-button")).click();
clickNewsletterNextButton = async () =>
await element(by.id("newsletter-button")).click();
clickEmailNextButton = async (testBrowser: ProtractorBrowser) =>
await testBrowser.element(by.id("email-button")).click();
clickPasswordNextButton = async (testBrowser: ProtractorBrowser) =>
await testBrowser.element(by.id("password-button")).click();
clickNewsletterNextButton = async (testBrowser: ProtractorBrowser) =>
await testBrowser.element(by.id("newsletter-button")).click();
getParagraphText = async (testBrowser: ProtractorBrowser) =>
await testBrowser.element(by.className("heading-medium")).getText();
getNewsletterText = (testBrowser: ProtractorBrowser) =>
testBrowser.element(by.className("register-step__newsletter-input"));
enterEmail = async (testBrowser: ProtractorBrowser, keys: string) =>
await testBrowser.element(by.id("Register Form.email")).sendKeys(keys);
await testBrowser.element(by.id("RegisterForm.email")).sendKeys(keys);
enterPassword = async (testBrowser: ProtractorBrowser, keys: string) =>
await testBrowser.element(by.id("Register Form.password")).sendKeys(keys);
await testBrowser.element(by.id("RegisterForm.password")).sendKeys(keys);
enterPasswordConfirm = async (testBrowser: ProtractorBrowser, keys: string) =>
await testBrowser
.element(by.id("Register Form.passwordConfirm"))
.element(by.id("RegisterForm.passwordConfirm"))
.sendKeys(keys);
enterServerURL = async (testBrowser: ProtractorBrowser, keys: string) =>
await testBrowser.element(by.id("userUrl")).sendKeys(keys);
submitByEnter = async (testBrowser: ProtractorBrowser) => {
const passInput = testBrowser.element(by.id("Register Form.password"));
const passInput = testBrowser.element(by.id("RegisterForm.password"));
await browser.wait(
EC.presenceOf(passInput),
DEFAULT_WAIT,
......@@ -117,11 +117,11 @@ export class ConfirmEmailPage {
}
export class AccountPage {
navigateTo = async () => await browser.get(browser.baseUrl + "/account");
navigateTo = async () => await browser.get("/account");
navigateToChangePass = async () =>
await browser.get(browser.baseUrl + "/account/change-password");
await browser.get("/account/change-password");
navigateToPassword = async () =>
await browser.get(browser.baseUrl + "/account/change-password");
await browser.get("/account/change-password");
resetPasswordButton = () =>
element(
by.cssContainingText(".account__item-heading", "Change Account Password")
......
......@@ -55,7 +55,7 @@ describe("List, Add, Edit and Remove Groups", () => {
it("in groups page and logged in", async () => {
const currentUrl = await browser.getCurrentUrl();
expect(currentUrl).toEqual(browser.baseUrl + "groups");
expect(currentUrl.endsWith("groups")).toBeTruthy();
});
it("Add groups", async () => {
......@@ -64,7 +64,7 @@ describe("List, Add, Edit and Remove Groups", () => {
let groupTitleElem: ElementFinder;
const displayFrom = async () => {
await groupsPage.newGroup();
await groupsPage.newGroup(browser);
await browser.wait(
EC.presenceOf(formEle),
DEFAULT_WAIT,
......
import { browser, by, element, Key } from "protractor";
import { browser, by, element, Key, ProtractorBrowser } from "protractor";
export class GroupsPage {
navigateTo = async () => await browser.get(browser.baseUrl + "groups");
navigateTo = async () => await browser.get("/groups");
getNewGroupBtnElem = () => element(by.id("showAddGroupform"));
// new form
cancelFormByClick = async () => await element(by.className("group-form__cancel-btn")).click();
enterGroupName = async (name: string) => await element(by.id("nameInput")).sendKeys(name);
newGroup = async () => await element(by.id("showAddGroupform")).click();
cancelFormByClick = async () =>
await element(by.className("group-form__cancel-btn")).click();
enterGroupName = async (name: string) =>
await element(by.id("nameInput")).sendKeys(name);
newGroup = async (testBrowser: ProtractorBrowser) =>
await testBrowser.element(by.id("showAddGroupform")).click();
// edit form
deleteGroupByClick = async () => await element(by.className("group-form__delete-btn")).click();
deleteGroupByClick = async () =>
await element(by.className("group-form__delete-btn")).click();
editGroupName = async (name: string) => await this.enterGroupName(name);
getManageGroupToggleBtnElem = () => element(by.id("manageSecretBtn"));
......@@ -20,18 +24,23 @@ export class GroupsPage {
getFormElem = () => element(by.className("secret__details"));
getGroupTitleElem = () => element(by.css(".secret__heading .secret__title"));
getNameFormInputElem = () => element(by.id("nameInput"));
getNewlyCreatedGroupElem = () => element.all(by.css(".secret-list-item")).last();
getNewlyCreatedGroupElem = () =>
element.all(by.css(".secret-list-item")).last();
saveButton = () => element(by.className("group-form__save-btn"));
saveGroupByClick = async () => await element(by.className("group-form__save-btn")).click();
submitGroupByEnter = async () => await element(by.id("nameInput")).sendKeys(Key.ENTER);
saveGroupByClick = async () =>
await element(by.className("group-form__save-btn")).click();
submitGroupByEnter = async () =>
await element(by.id("nameInput")).sendKeys(Key.ENTER);
membersInput = () => element(by.id("membersInput"));
// specific member
groupMember = (name: string) => element(by.cssContainingText(".option", `${name}`));
groupMember = (name: string) =>
element(by.cssContainingText(".option", `${name}`));
getMembersInput = () => element(by.css(".multiple input"));
membersList = () => element(by.tagName("select-dropdown"));
getDropDownItem = (name: string) => element(by.xpath(`//span[contains(string(), "${name}")]`));
getDropDownItem = (name: string) =>
element(by.xpath(`//span[contains(string(), "${name}")]`));
}
......@@ -24,9 +24,8 @@ import { DEFAULT_WAIT, login, register } from "./shared";
.manage()
.timeouts()
.setScriptTimeout(30 * 1000);
(global as any).screenshotBrowsers["browser2"] = browser2;
// (global as any).screenshotBrowsers["browser1"] = browser;
secretsPage = new SecretsPage();
groupsPage = new GroupsPage();
browser.sleep(50);
......@@ -50,24 +49,25 @@ import { DEFAULT_WAIT, login, register } from "./shared";
afterAll(async () => {
browser2.close();
delete (global as any).screenshotBrowsers.browser2;
browser.executeScript("window.localStorage.clear();");
browser.executeScript("window.sessionStorage.clear();");
});
it("in groups page and logged in", async () => {
groupsPage = new GroupsPage();
await groupsPage.navigateTo();
const currentUrl = await browser.getCurrentUrl();
expect<any>(currentUrl).toEqual(browser.baseUrl + "groups");
expect(currentUrl.endsWith("groups")).toBeTruthy();
});
it("It creates a group and adds second user to group", async () => {
// Create second user
await register(browser2, USERNAME2, PASSWORD2, PASSWORD2);
await browser2.refresh();
await login(browser2, USERNAME2, PASSWORD2);
await browser.refresh();
await groupsPage.newGroup();
// First user creates group
browser.refresh(); // Cheap way to refresh contacts data
await groupsPage.newGroup(browser);
await groupsPage.enterGroupName(groupName);
const elem = groupsPage.getMembersInput();
......@@ -75,6 +75,7 @@ import { DEFAULT_WAIT, login, register } from "./shared";
.actions()
.mouseMove(elem)
.click();
// First user adds second user to group
await groupsPage.getMembersInput().sendKeys(USERNAME2);
const groupClick = groupsPage.getDropDownItem(USERNAME2);
await groupClick.click();
......@@ -101,7 +102,6 @@ import { DEFAULT_WAIT, login, register } from "./shared";
"Can see list"
);
await searchElem.isDisplayed();
// const formElement = secretsPage.getFormElem(browser);
await secretsPage.selectAddNewPassword();
await secretsPage.enterName(secretName);
......@@ -113,86 +113,39 @@ import { DEFAULT_WAIT, login, register } from "./shared";
await group.click();
await secretsPage.submitByEnter();
// await browser.wait(
// EC.stalenessOf(formElement),
// DEFAULT_WAIT,
// "secret not saved and form is still open"
// );
// const secret = await secretsPage.getSecretText(secretName);
// expect<any>(secret).toBe(secretName);
});
it("Decrypts the password", async () => {
it("Decrypts the shared password from a group", async () => {
browser2.refresh();
const currentSecretToggleBtnElem = secretsPage.getToggleButton(
browser2,
secretName
);
const button = currentSecretToggleBtnElem
.element(secretsPage.getManageSecretToggleBtnElem())
.element(secretsPage.getManageSecretToggleBtnElem(browser2))
.locator();
button.click();
const formElement = secretsPage.getFormElem(browser);
await EC.browser.wait(
const formElement = secretsPage.getFormElem(browser2);
await browser.wait(
EC.presenceOf(formElement),
DEFAULT_WAIT,
"Secret Form not present"
);
const currentPasswordToggleBtnElem = browser.element(
const currentPasswordToggleBtnElem = browser2.element(
by.className("t-password-viewer__actions")
);
currentPasswordToggleBtnElem.click();
const passwordNameText = async () => {
const passwordNameElem = await secretsPage.getPasswordNameElem(browser);
return passwordNameElem;
};
expect<any>(await passwordNameText()).toBe("hunter2");
const closeButton = secretsPage.getManageSecretToggleBtnElem();
await closeButton.click();
});
it("It decrypts shared group password", async () => {
await browser2.sleep(50);
await secretsPage.navigateTo(browser2);
const E = browser2.ExpectedConditions;
const div = secretsPage.getToggleButton(browser2, secretName);
const button = div.element(by.id("manageSecretBtn"));
button.click();
const formElement = secretsPage.getFormElem(browser2);
await E.browser.wait(
E.presenceOf(formElement),
DEFAULT_WAIT,
"Secret Form not displayed"
);
await browser2.sleep(50);
const currentPasswordToggleBtnElem = secretsPage.getShowPasswordToggleBtnElem(
browser2
);
await E.browser.wait(
E.presenceOf(currentPasswordToggleBtnElem),
DEFAULT_WAIT,
"Secret Form not displayed"
);
await currentPasswordToggleBtnElem.click();
await browser2.sleep(50);
const passwordNameText = async () => {
const passwordNameElem = await secretsPage.getPasswordNameElem(browser2);
return passwordNameElem;
};
expect<any>(await passwordNameText()).toBe("hunter2");
expect(await passwordNameText()).toBe("hunter2");
const closeButton = secretsPage.getManageSecretToggleBtnElem();
const closeButton = secretsPage.getManageSecretToggleBtnElem(browser2);
await closeButton.click();
});
});
......@@ -7,7 +7,7 @@ export class SecretsPage {
getSearchElem = () => element(by.id("search"));
navigateTo = async (testBrowser: ProtractorBrowser) =>
await testBrowser.get(testBrowser.baseUrl + "/list");
await testBrowser.get("/list");
selectAddNewPassword = async () =>
await element(by.id("add-new-password-button")).click();
enterName = async (keys: string) =>
......@@ -35,7 +35,8 @@ export class SecretsPage {
getAllSecretsElem = () => element.all(by.css(".secret-list-item"));
getManageSecretToggleBtnElem = () => element(by.id("manageSecretBtn"));
getManageSecretToggleBtnElem = (testBrowser: ProtractorBrowser) =>
testBrowser.element(by.id("manageSecretBtn"));
getPasswordNameElem = async (testBrowser: ProtractorBrowser) =>
await testBrowser
......
......@@ -28,10 +28,10 @@ export async function register(
const registerPage = new RegisterPage();
expect(await registerPage.getParagraphText(testBrowser)).toEqual("Sign Up");
await registerPage.enterEmail(testBrowser, username);
await registerPage.clickEmailNextButton();
await registerPage.clickEmailNextButton(testBrowser);
await registerPage.enterPassword(testBrowser, password);
await registerPage.enterPasswordConfirm(testBrowser, passwordConfirm);
await registerPage.clickPasswordNextButton();
await registerPage.clickPasswordNextButton(testBrowser);
if (isExtension) {
await registerPage.enterServerURL(testBrowser, SERVER_URL);
}
......@@ -41,7 +41,7 @@ export async function register(
DEFAULT_WAIT,
"Newsletter text should be shown"
);
await registerPage.clickNewsletterNextButton();
await registerPage.clickNewsletterNextButton(testBrowser);
const confirmEmailPage = new ConfirmEmailPage();
const codeElem = confirmEmailPage.getCodeElem(testBrowser);
await EC.browser.wait(
......
{
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2016"
],
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types":[
"jasmine",
"node"
]
}
}
{
"extends": "../tsconfig.json",
"compilerOptions": {
"sourceMap": true,
"declaration": false,
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"lib": [
"es2016"
],
"outDir": "../out-tsc/e2e",
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types":[
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
......
// Karma configuration file, see link for more information
// https://karma-runner.github.io/0.13/config/configuration-file.html
module.exports = function (config) {
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
basePath: "",
frameworks: ["jasmine", "@angular-devkit/build-angular"],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('karma-webdriver-launcher'),
require('@angular-devkit/build-angular/plugins/karma')
require("karma-jasmine"),
require("karma-chrome-launcher"),
require("karma-jasmine-html-reporter"),
require("karma-coverage-istanbul-reporter"),
require("karma-webdriver-launcher"),
require("@angular-devkit/build-angular/plugins/karma")
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
customLaunchers: {
'selenium': {
base: 'WebDriver',
selenium: {
base: "WebDriver",
config: {
hostname: 'selenium-chrome'
hostname: "selenium-chrome"
},
browserName: 'chrome'
browserName: "chrome"
},
Chromium_CI: {
base: "Chromium",
flags: [
"--no-sandbox",
"--headless",
"--disable-gpu",
"--remote-debugging-port=9222"
]
}
},
hostname: process.env.DOCKER ? process.env.HOSTNAME : undefined,
files: [
],
preprocessors: {
},
files: [],
preprocessors: {},
mime: {
'text/x-typescript': ['ts', 'tsx']
"text/x-typescript": ["ts", "tsx"]
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, 'coverage'), reports: [ 'html', 'lcovonly' ],
dir: require("path").join(__dirname, "coverage"),
reports: ["html", "lcovonly"],
fixWebpackSourcePaths: true
},
angularCli: {
environment: 'dev'
environment: "dev"
},
reporters: config.angularCli && config.angularCli.codeCoverage
? ['progress', 'coverage-istanbul']
: ['progress', 'kjhtml'],
reporters:
config.angularCli && config.angularCli.codeCoverage
? ["progress", "coverage-istanbul"]
: ["progress", "kjhtml"],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
browsers: ["Chrome"],
singleRun: false,
// to stop timeout in CI
......@@ -58,5 +65,5 @@ module.exports = function (config) {
browserDisconnectTimeout: 60 * 1000,
browserDisconnectTolerance: 5,
browserNoActivityTimeout: 60 * 10000
})
}
});
};
......@@ -15,7 +15,7 @@
"ext:lint": "web-ext -s dist/ lint --self-hosted",
"ext:build": "web-ext -s dist/ build --ignore-files assets/icon/manifest.json",
"ext:publish_firefox": "shipit firefox dist-ext",
"e2e": "ng e2e -s false",
"e2e": "ng e2e",
"e2e:docker": "yarn e2e --webdriver-update false",
"bundle-report": "webpack-bundle-analyzer dist/stats.json",
"compodoc": "./node_modules/.bin/compodoc -p tsconfig.json",
......@@ -60,7 +60,6 @@
"normalize-scss": "~7.0.0",
"papaparse": "~4.5.0",
"passit-sdk-js": "2.1.0",
"protractor-screenshoter-plugin": "^0.7.0",
"rxjs": "~6.1.0",
"rxjs-compat": "^6.1.0",
"stream": "^0.0.2",
......@@ -75,6 +74,7 @@
"@angular/compiler-cli": "^6.0.0",
"@compodoc/compodoc": "~1.1.3",
"@types/jasmine": "~2.8.2",
"@types/jasminewd2": "~2.0.3",
"@types/node": "~8.5.2",
"@wext/shipit": "^0.1.3",
"breakpoint-sass": "~2.7.1",
......
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
const { DOCKER, FIREFOX, EXTENSION } = process.env;
if (FIREFOX && EXTENSION) {
throw new Error('Can\'t test Firefox with extension at the moment. https://gitlab.com/passit/passit-frontend/issues/13')
}
exports.config = {
allScriptsTimeout: 39000, // 39s to make it easier to identify
specs: [
'./e2e/**/*.e2e-spec.ts'
],
capabilities: FIREFOX ? {
'browserName': 'firefox',
} : {
'browserName': 'chrome',
'chromeOptions': {
'args': EXTENSION ? ['--no-sandbox', `--load-extension=/dist/dist/`] : ['--no-sandbox']
}
},
directConnect: !DOCKER,
// chrome extension ID remain constant if the path to the ext remains constant
// https://stackoverflow.com/a/26058672
baseUrl: EXTENSION ? 'chrome-extension://hncijbkokpgjokbcdmcelmfndciahmkf/index.html#/' : (DOCKER ? 'http://web:4200/' : 'http://localhost:4200/'),
seleniumAddress: DOCKER ? `http://selenium-${FIREFOX ? 'firefox' : 'chrome'}:4444/wd/hub/` : undefined,
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 50000,
print: function() {}
},
useAllAngular2AppRoots: true,
beforeLaunch: function() {
require('ts-node').register({
project: 'e2e/tsconfig.e2e.json'
});
},
onPrepare() {
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
return global.browser.getProcessedConfig().then(function (config) {});
},
plugins: [{
package: 'protractor-screenshoter-plugin',
screenshotOnExpect: 'failure',
screenshotOnSpec: 'failure',
}],
SELENIUM_PROMISE_MANAGER: false
};
......@@ -12,7 +12,6 @@
<div class="form-field form-field--large">
<label for="oldPassword" class="form-field__label">Current Password</label>
<input type="password"
id="oldPassword"
[ngrxFormControlState]="form.controls.oldPassword"
class="form-field__input form-field__input--one-action"
[class.form-field__input--invalid]="form.errors._oldPassword && form.isSubmitted"
......@@ -33,7 +32,6 @@
<div class="form-field form-field--large">