Merge pull request #960 from mozilla/vnext-integration-tests

Vnext integration tests
This commit is contained in:
Danny Coates 2018-10-04 15:18:30 -07:00 committed by GitHub
commit 23932c0d4c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 9811 additions and 4834 deletions

1
.gitignore vendored
View file

@ -12,3 +12,4 @@ ios/send-ios/assets/ios.js
ios/send-ios/assets/vendor.js
ios/send-ios.xcodeproj/project.xcworkspace/xcuserdata/*
ios/send-ios.xcodeproj/xcuserdata/*
test/integration/downloads

View file

@ -1,7 +0,0 @@
# autogenerated pyup.io config file
# see https://pyup.io/docs/configuration/ for all available options
schedule: every week
requirements:
- test/integration/Pipfile
- test/integration/pipenv.txt

View file

@ -6,10 +6,10 @@ jobs:
steps:
- checkout
- restore_cache:
key: send-{{ checksum "package-lock.json" }}
key: send-build-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
key: send-{{ checksum "package-lock.json" }}
key: send-build-{{ checksum "package-lock.json" }}
paths:
- node_modules
- run: npm run build
@ -23,10 +23,10 @@ jobs:
steps:
- checkout
- restore_cache:
key: send-{{ checksum "package-lock.json" }}
key: send-test-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
key: send-{{ checksum "package-lock.json" }}
key: send-test-{{ checksum "package-lock.json" }}
paths:
- node_modules
- run: npm run lint
@ -34,21 +34,19 @@ jobs:
- store_artifacts:
path: coverage
integration_tests:
machine: true
docker:
- image: circleci/node:10
- image: selenium/standalone-firefox
steps:
- checkout
- attach_workspace:
at: .
- run:
name: Install Docker Compose
command: |
set -x
pip install docker-compose>=1.18
docker-compose --version
- run:
command: npm run test-integration
- store_artifacts:
path: coverage/send-test.html
- restore_cache:
key: send-int-{{ checksum "package-lock.json" }}
- run: npm install
- save_cache:
key: send-int-{{ checksum "package-lock.json" }}
paths:
- node_modules
- run: npm run circleci-test-integration
deploy_dev:
machine: true
steps:
@ -92,12 +90,12 @@ workflows:
ignore:
- master
- vnext
# - integration_tests:
# filters:
# branches:
# ignore: master
# requires:
# - build
- integration_tests:
filters:
branches:
ignore: master
requires:
- build
build_and_deploy_dev:
jobs:
- build:

View file

@ -10,17 +10,10 @@ services:
- REDIS_HOST=redis
redis:
image: redis:alpine
selenium:
selenium-firefox:
image: b4handjr/selenium-firefox
ports:
- "${VNC_PORT:-5900}:5900"
shm_size: 2g
integration-tests:
build: ./test/integration
environment:
- BASE_URL=${BASE_URL:-http://web:1443}
links:
- web
- selenium
volumes:
- "./coverage:/coverage"
- .:/code

13359
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -22,10 +22,11 @@
"contributors": "git shortlog -s | awk -F\\t '{print $2}' > CONTRIBUTORS",
"release": "npm-run-all contributors changelog",
"test": "npm-run-all test:*",
"test:backend": "nyc mocha --reporter=min test/backend",
"test:frontend": "cross-env NODE_ENV=development node test/frontend/runner.js && nyc report --reporter=html",
"test-integration": "docker-compose up --abort-on-container-exit --exit-code-from integration-tests --build --remove-orphans --quiet-pull && docker-compose down",
"test-integration-stage": "cross-env BASE_URL=https://send.stage.mozaws.net npm run test-integration",
"test:backend": "nyc --reporter=lcovonly mocha --reporter=min test/backend",
"test:frontend": "cross-env NODE_ENV=development node test/frontend/runner.js",
"test:report": "nyc report --reporter=html",
"test-integration": "cross-env NODE_ENV=development wdio test/wdio.docker.conf.js",
"circleci-test-integration": "cross-env NODE_ENV=development wdio test/wdio.circleci.conf.js",
"start": "npm run clean && cross-env NODE_ENV=development FXA_CLIENT_ID=fced6b5e3f4c66b9 BASE_URL=http://localhost:8080 webpack-dev-server --mode=development",
"android": "cross-env ANDROID=1 npm start",
"prod": "node server/bin/prod.js"
@ -115,6 +116,13 @@
"svgo-loader": "^2.2.0",
"testpilot-ga": "^0.3.0",
"val-loader": "^1.1.1",
"wdio-docker-service": "^1.4.2",
"wdio-dot-reporter": "0.0.10",
"wdio-firefox-profile-service": "^0.1.3",
"wdio-mocha-framework": "^0.6.3",
"wdio-sauce-service": "^0.4.11",
"wdio-spec-reporter": "^0.1.5",
"webdriverio": "^4.13.2",
"webpack": "^4.20.2",
"webpack-cli": "^3.1.2",
"webpack-dev-middleware": "^3.4.0",

View file

@ -25,7 +25,8 @@ module.exports = function(app) {
Object.setPrototypeOf(Combo.prototype, reporters.HTML.prototype)
mocha.setup({
ui: 'bdd',
reporter: Combo
reporter: Combo,
timeout: 5000
})
</script>
<script src="/jsconfig.js"></script>
@ -37,8 +38,7 @@ module.exports = function(app) {
<body>
<div id="mocha"></div>
<script>
mocha.checkLeaks();
const runner = mocha.run();
window.runner = mocha.run();
</script>
</body>
</html>

View file

@ -1,10 +0,0 @@
FROM ubuntu:xenial
RUN apt-get update && \
apt-get install -y python-pip python-dev && \
pip install tox
COPY . /integration
WORKDIR /integration
RUN tox --notest
CMD ["tox", "-e", "integration-tests"]

View file

@ -1,76 +0,0 @@
# -*- coding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
"""Configuration files for pytest."""
import pytest
import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry
from pages.desktop.download import Download
from pages.desktop.home import Home
@pytest.fixture
def firefox_options(firefox_options, download_location_dir):
"""Firefox options."""
firefox_options.set_preference("browser.download.panel.shown", False)
firefox_options.set_preference(
"browser.helperApps.neverAsk.openFile", "text/plain")
firefox_options.set_preference(
"browser.helperApps.neverAsk.saveToDisk", "text/plain")
firefox_options.set_preference("browser.download.folderList", 2)
firefox_options.set_preference(
"browser.download.dir", "{0}".format(download_location_dir))
firefox_options.add_argument('-foreground')
firefox_options.log.level = 'trace'
return firefox_options
@pytest.fixture(scope='session', autouse=True)
def _verify_url(request, base_url):
"""Verifies the base URL"""
verify = request.config.option.verify_base_url
if base_url and verify:
session = requests.Session()
retries = Retry(backoff_factor=0.1,
status_forcelist=[500, 502, 503, 504])
session.mount(base_url, HTTPAdapter(max_retries=retries))
session.get(base_url, verify=False)
@pytest.fixture
def download_location_dir(tmpdir):
"""Directory for downloading sample file."""
return tmpdir.mkdir('test_download')
@pytest.fixture
def upload_location_dir(tmpdir):
"""Directory for uploading sample file."""
return tmpdir.mkdir('test_upload')
@pytest.fixture
def test_file(upload_location_dir):
"""Create test upload/download file."""
setattr(test_file, 'name', 'sample.txt')
setattr(test_file, 'location', upload_location_dir.join(test_file.name))
return test_file
@pytest.fixture
def download_file(upload_file):
"""Uploads and downloads a file"""
download = Download(upload_file.selenium, upload_file.file_url).open()
download.download_btn.click()
return download
@pytest.fixture
def upload_file(selenium, base_url, download_location_dir, test_file):
"""Upload file fixture."""
home = Home(selenium, base_url).open()
test_file.location.write('This is a test! This is a test!')
return home.upload_area("{0}".format(test_file.location.realpath()))

View file

@ -0,0 +1,51 @@
/* global browser document */
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const DownloadPage = require('./pages/desktop/download_page');
const HomePage = require('./pages/desktop/home_page');
const SharePage = require('./pages/desktop/share_page');
describe('Firefox Send', function() {
const downloadDir =
browser.desiredCapabilities['moz:firefoxOptions']['prefs'][
'browser.download.dir'
];
const testFilesPath = path.join(__dirname, 'fixtures');
const testFiles = fs.readdirSync(testFilesPath);
beforeEach(function() {
browser.url('/');
browser.execute(() => {
document.getElementById('file-upload').style.display = 'block';
});
browser.waitForExist('#file-upload');
});
testFiles.forEach(file => {
it(`should upload and download files, file: ${file}`, function() {
browser.execute(() => {
document.getElementById('file-upload').style.display = 'block';
});
browser.waitForExist('#file-upload');
const homePage = new HomePage();
browser.chooseFile('#file-upload', `${testFilesPath}/${file}`);
browser.click(homePage.readyToSend);
const sharePage = new SharePage();
browser.waitForExist(sharePage.fileUrl);
browser.url(browser.getValue(sharePage.fileUrl));
const downloadPage = new DownloadPage();
downloadPage.waitForPageToLoad();
downloadPage.downloadBtn();
// Wait for download to complete
browser.waitUntil(() => {
browser.waitForExist(downloadPage.downloadComplete);
return (
browser.getText(downloadPage.downloadComplete) === 'DOWNLOAD COMPLETE'
);
});
assert.ok(fs.existsSync(path.join(downloadDir, file)));
});
});
});

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
THIS IS A TEST!

View file

@ -0,0 +1,54 @@
/* global browser */
const assert = require('assert');
const HomePage = require('./pages/desktop/home_page');
describe('Firefox Send homepage', function() {
const baseUrl = browser.options['baseUrl'];
const legalLinks = [
'legal',
'about',
'legal',
'cookies',
'report-infringement'
];
const socialLinks = ['github', 'twitter', 'mozilla'];
beforeEach(function() {
browser.url('/');
browser.pause(500);
});
it('should have the right title', function() {
assert.equal(browser.getTitle(), 'Firefox Send');
});
legalLinks.forEach((link, i) => {
it(`should navigate to the correct legal pages, page: ${link}`, function() {
const homePage = new HomePage();
// Click links on bottom of page
const els = browser.elements(homePage.legalLinks);
browser.elementIdClick(els.value[i].ELEMENT);
// Wait for page to load
browser.waitUntil(() => {
const url = browser.getUrl();
return url !== baseUrl;
});
assert.ok(browser.getUrl().includes(link));
});
});
socialLinks.forEach((link, i) => {
it(`should navigate to the correct social pages, page: ${link}`, function() {
const homePage = new HomePage();
// Click links on bottom of page
const els = browser.elements(homePage.socialLinks);
browser.elementIdClick(els.value[i].ELEMENT);
// Wait for page to load
browser.waitUntil(() => {
const url = browser.getUrl();
return url !== baseUrl;
});
assert.ok(browser.getUrl().includes(link));
});
});
});

View file

@ -1,41 +0,0 @@
from pypom import Page, Region
from selenium.webdriver.common.by import By
class Base(Page):
"""Base object model."""
_url = '{base_url}'
_send_logo_locator = (By.CLASS_NAME, 'logo')
def __init__(self, selenium, base_url, locale='en-US', **kwargs):
super(Base, self).__init__(
selenium, base_url, locale=locale, timeout=20, **kwargs)
def wait_for_page_to_load(self):
self.wait.until(
lambda _: self.find_element(
*self._send_logo_locator).is_displayed())
return self
@property
def footer(self):
return self.Footer(self)
class Footer(Region):
_root_element = (By.CLASS_NAME, 'footer')
_legal_links = (By.CLASS_NAME, 'legalSection__link')
@property
def links(self):
return [self.Links(self, el) for el in self.find_elements(
*self._legal_links)]
class Links(Region):
@property
def name(self):
return self.root.text.split()[0]
def click(self):
self.root.click()

View file

@ -1,17 +0,0 @@
from selenium.webdriver.common.by import By
from pages.desktop.base import Base
class Download(Base):
"""Download page object model."""
_download_button_locator = (By.CLASS_NAME, 'btn--download')
def wait_for_page_to_load(self):
self.wait.until(lambda _: self.download_btn.is_displayed())
@property
def download_btn(self):
"""Download button."""
return self.find_element(*self._download_button_locator)

View file

@ -0,0 +1,34 @@
/* global browser */
const Page = require('./page');
class DownloadPage extends Page {
constructor() {
super();
this.downloadBtnLocator = '.btn--download';
this.downloadCompletedLocator = '.btn--complete';
}
/**
* @function waitForPageToLoad
* @returns {Object} An object representing the page.
* @throws ElementNotFound
*/
waitForPageToLoad() {
browser.waitUntil(() => {
browser.waitForExist(this.downloadBtnLocator);
const el = browser.element(this.downloadBtnLocator);
return browser.elementIdDisplayed(el.value.ELEMENT);
});
return this;
}
downloadBtn() {
this.waitForPageToLoad();
return browser.click(this.downloadBtnLocator);
}
get downloadComplete() {
return this.downloadCompletedLocator;
}
}
module.exports = DownloadPage;

View file

@ -1,26 +0,0 @@
from selenium.webdriver.common.by import By
from pages.desktop.base import Base
class Home(Base):
"""Firefox Send Home page object model."""
_upload_area_locator = (By.ID, 'file-upload')
_upload_button_locator = (By.CLASS_NAME, 'btn--file')
@property
def upload_btn(self):
"""Upload button."""
return self.find_element(*self._upload_button_locator)
def upload_area(self, path, cancel=False):
"""Area that allows for drag and drop uploading.
Returns Progress Object.
"""
self.find_element(*self._upload_area_locator).send_keys(path)
from pages.desktop.progress import Progress
return Progress(
self.selenium, self.base_url).wait_for_page_to_load(
cancel_after_load=cancel)

View file

@ -0,0 +1,23 @@
const Page = require('./page');
class HomePage extends Page {
constructor() {
super();
this.legalSectionLinks = '.legalSection .legalSection__link';
this.readyToSendLocator = 'div#page-one button.btn';
this.socialLinksLocator = '.socialSection__link';
}
get legalLinks() {
return this.legalSectionLinks;
}
get readyToSend() {
return this.readyToSendLocator;
}
get socialLinks() {
return this.socialLinksLocator;
}
}
module.exports = HomePage;

View file

@ -0,0 +1,17 @@
/* global browser */
class Page {
constructor() {}
open(path) {
browser.url(path);
this.waitForPageToLoad();
}
/**
* @function waitForPageToLoad
* @returns {Object} An object representing the page.
* @throws ElementNotFound
*/
waitForPageToLoad() {}
}
module.exports = Page;

View file

@ -1,25 +0,0 @@
from selenium.webdriver.common.by import By
from pages.desktop.base import Base
class Progress(Base):
"""Progress page object model."""
_cancel_button = (By.ID, 'cancel-upload')
_progress_icon_locator = (By.CLASS_NAME, 'progress__bar')
def wait_for_page_to_load(self, cancel_after_load=False):
self.wait.until(
lambda _: self.find_element(
*self._progress_icon_locator).is_displayed())
if cancel_after_load:
self.cancel_btn.click()
return
from pages.desktop.share import Share
return Share(self.selenium, self.base_url).wait_for_page_to_load()
@property
def cancel_btn(self):
"""Cancel upload button."""
return self.find_element(*self._cancel_button)

View file

@ -0,0 +1,31 @@
/* global browser */
const Page = require('./page');
const SharePage = require('./share_page');
class ProgressPage extends Page {
constructor() {
super();
this.cancelBtnLocator = '.uploadCancel';
this.progressIconLocator = '.btn--stripes';
}
/**
* @function waitForPageToLoad
* @returns {Object} An object representing the Share page.
* @throws ElementNotFound
*/
waitForPageToLoad() {
browser.waitUntil(() => {
browser.waitForExist(this.progressIconLocator);
const el = browser.element(this.progressIconLocator);
return browser.elementIdDisplayed(el.value.ELEMENT);
});
const sharePage = new SharePage();
return sharePage.waitForPageToLoad();
}
get cancelBtn() {
return this.cancelBtnLocator;
}
}
module.exports = ProgressPage;

View file

@ -1,22 +0,0 @@
from selenium.webdriver.common.by import By
from pages.desktop.base import Base
class Share(Base):
"""SHare page object model."""
_share_page_locator = (By.CLASS_NAME, 'sharePage')
_share_url_locator = (By.ID, 'fileUrl')
def wait_for_page_to_load(self):
self.wait.until(
lambda _: self.find_element(
*self._share_page_locator).is_displayed())
return self
@property
def file_url(self):
"""File uploaded URL."""
return self.find_element(
*self._share_url_locator).get_property('value')

View file

@ -0,0 +1,24 @@
/* global browser */
const Page = require('./page');
class SharePage extends Page {
constructor() {
super();
this.sharePageLocator = '#shareWrapper';
this.shareUrlLocator = '#fileUrl';
}
waitForPageToLoad() {
browser.waitUntil(() => {
browser.waitForExist(this.sharePageLocator);
const el = browser.element(this.sharePageLocator);
return browser.elementIdDisplayed(el.value.ELEMENT);
});
return this;
}
get fileUrl() {
return this.shareUrlLocator;
}
}
module.exports = SharePage;

View file

@ -0,0 +1,22 @@
/* global browser document */
const assert = require('assert');
const ProgressPage = require('./pages/desktop/progress_page');
const HomePage = require('./pages/desktop/home_page');
describe('Firefox Send progress page', function() {
beforeEach(function() {
browser.url('/');
});
it('should show an icon while an upload is in progress', function() {
browser.execute(() => {
document.getElementById('file-upload').style.display = 'block';
});
browser.waitForExist('#file-upload');
const homePage = new HomePage();
browser.chooseFile('#file-upload', __filename);
browser.click(homePage.readyToSend);
const progressPage = new ProgressPage();
assert.ok(progressPage.waitForPageToLoad());
});
});

View file

@ -1,8 +0,0 @@
selenium==3.13.0
flake8==3.5.0
flake8-isort==2.5
PyPOM==2.0.0
pytest==3.6.3
pytest-html==1.19.0
pytest-selenium==1.13.0
pytest-xdist==1.22.2

View file

@ -0,0 +1,464 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Test Report</title>
<style>body {
font-family: Helvetica, Arial, sans-serif;
font-size: 12px;
min-width: 1200px;
color: #999;
}
h1 {
font-size: 24px;
color: black;
}
h2 {
font-size: 16px;
color: black;
}
p {
color: black;
}
a {
color: #999;
}
table {
border-collapse: collapse;
}
/******************************
* SUMMARY INFORMATION
******************************/
#environment td {
padding: 5px;
border: 1px solid #E6E6E6;
}
#environment tr:nth-child(odd) {
background-color: #f6f6f6;
}
/******************************
* TEST RESULT COLORS
******************************/
span.passed, .passed .col-result {
color: green;
}
span.skipped, span.xfailed, span.rerun, .skipped .col-result, .xfailed .col-result, .rerun .col-result {
color: orange;
}
span.error, span.failed, span.xpassed, .error .col-result, .failed .col-result, .xpassed .col-result {
color: red;
}
/******************************
* RESULTS TABLE
*
* 1. Table Layout
* 2. Extra
* 3. Sorting items
*
******************************/
/*------------------
* 1. Table Layout
*------------------*/
#results-table {
border: 1px solid #e6e6e6;
color: #999;
font-size: 12px;
width: 100%
}
#results-table th, #results-table td {
padding: 5px;
border: 1px solid #E6E6E6;
text-align: left
}
#results-table th {
font-weight: bold
}
/*------------------
* 2. Extra
*------------------*/
.log:only-child {
height: inherit
}
.log {
background-color: #e6e6e6;
border: 1px solid #e6e6e6;
color: black;
display: block;
font-family: "Courier New", Courier, monospace;
height: 230px;
overflow-y: scroll;
padding: 5px;
white-space: pre-wrap
}
div.image {
border: 1px solid #e6e6e6;
float: right;
height: 240px;
margin-left: 5px;
overflow: hidden;
width: 320px
}
div.image img {
width: 320px
}
.collapsed {
display: none;
}
.expander::after {
content: " (show details)";
color: #BBB;
font-style: italic;
cursor: pointer;
}
.collapser::after {
content: " (hide details)";
color: #BBB;
font-style: italic;
cursor: pointer;
}
/*------------------
* 3. Sorting items
*------------------*/
.sortable {
cursor: pointer;
}
.sort-icon {
font-size: 0px;
float: left;
margin-right: 5px;
margin-top: 5px;
/*triangle*/
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
}
.inactive .sort-icon {
/*finish triangle*/
border-top: 8px solid #E6E6E6;
}
.asc.active .sort-icon {
/*finish triangle*/
border-bottom: 8px solid #999;
}
.desc.active .sort-icon {
/*finish triangle*/
border-top: 8px solid #999;
}
</style></head>
<body onLoad="init()">
<script>/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
function toArray(iter) {
if (iter === null) {
return null;
}
return Array.prototype.slice.call(iter);
}
function find(selector, elem) {
if (!elem) {
elem = document;
}
return elem.querySelector(selector);
}
function find_all(selector, elem) {
if (!elem) {
elem = document;
}
return toArray(elem.querySelectorAll(selector));
}
function sort_column(elem) {
toggle_sort_states(elem);
var colIndex = toArray(elem.parentNode.childNodes).indexOf(elem);
var key;
if (elem.classList.contains('numeric')) {
key = key_num;
} else if (elem.classList.contains('result')) {
key = key_result;
} else {
key = key_alpha;
}
sort_table(elem, key(colIndex));
}
function show_all_extras() {
find_all('.col-result').forEach(show_extras);
}
function hide_all_extras() {
find_all('.col-result').forEach(hide_extras);
}
function show_extras(colresult_elem) {
var extras = colresult_elem.parentNode.nextElementSibling;
var expandcollapse = colresult_elem.firstElementChild;
extras.classList.remove("collapsed");
expandcollapse.classList.remove("expander");
expandcollapse.classList.add("collapser");
}
function hide_extras(colresult_elem) {
var extras = colresult_elem.parentNode.nextElementSibling;
var expandcollapse = colresult_elem.firstElementChild;
extras.classList.add("collapsed");
expandcollapse.classList.remove("collapser");
expandcollapse.classList.add("expander");
}
function show_filters() {
var filter_items = document.getElementsByClassName('filter');
for (var i = 0; i < filter_items.length; i++)
filter_items[i].hidden = false;
}
function add_collapse() {
// Add links for show/hide all
var resulttable = find('table#results-table');
var showhideall = document.createElement("p");
showhideall.innerHTML = '<a href="javascript:show_all_extras()">Show all details</a> / ' +
'<a href="javascript:hide_all_extras()">Hide all details</a>';
resulttable.parentElement.insertBefore(showhideall, resulttable);
// Add show/hide link to each result
find_all('.col-result').forEach(function(elem) {
var collapsed = get_query_parameter('collapsed') || 'Passed';
var extras = elem.parentNode.nextElementSibling;
var expandcollapse = document.createElement("span");
if (collapsed.includes(elem.innerHTML)) {
extras.classList.add("collapsed");
expandcollapse.classList.add("expander");
} else {
expandcollapse.classList.add("collapser");
}
elem.appendChild(expandcollapse);
elem.addEventListener("click", function(event) {
if (event.currentTarget.parentNode.nextElementSibling.classList.contains("collapsed")) {
show_extras(event.currentTarget);
} else {
hide_extras(event.currentTarget);
}
});
})
}
function get_query_parameter(name) {
var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
return match && decodeURIComponent(match[1].replace(/\+/g, ' '));
}
function init () {
reset_sort_headers();
add_collapse();
show_filters();
toggle_sort_states(find('.initial-sort'));
find_all('.sortable').forEach(function(elem) {
elem.addEventListener("click",
function(event) {
sort_column(elem);
}, false)
});
};
function sort_table(clicked, key_func) {
var rows = find_all('.results-table-row');
var reversed = !clicked.classList.contains('asc');
var sorted_rows = sort(rows, key_func, reversed);
/* Whole table is removed here because browsers acts much slower
* when appending existing elements.
*/
var thead = document.getElementById("results-table-head");
document.getElementById('results-table').remove();
var parent = document.createElement("table");
parent.id = "results-table";
parent.appendChild(thead);
sorted_rows.forEach(function(elem) {
parent.appendChild(elem);
});
document.getElementsByTagName("BODY")[0].appendChild(parent);
}
function sort(items, key_func, reversed) {
var sort_array = items.map(function(item, i) {
return [key_func(item), i];
});
var multiplier = reversed ? -1 : 1;
sort_array.sort(function(a, b) {
var key_a = a[0];
var key_b = b[0];
return multiplier * (key_a >= key_b ? 1 : -1);
});
return sort_array.map(function(item) {
var index = item[1];
return items[index];
});
}
function key_alpha(col_index) {
return function(elem) {
return elem.childNodes[1].childNodes[col_index].firstChild.data.toLowerCase();
};
}
function key_num(col_index) {
return function(elem) {
return parseFloat(elem.childNodes[1].childNodes[col_index].firstChild.data);
};
}
function key_result(col_index) {
return function(elem) {
var strings = ['Error', 'Failed', 'Rerun', 'XFailed', 'XPassed',
'Skipped', 'Passed'];
return strings.indexOf(elem.childNodes[1].childNodes[col_index].firstChild.data);
};
}
function reset_sort_headers() {
find_all('.sort-icon').forEach(function(elem) {
elem.parentNode.removeChild(elem);
});
find_all('.sortable').forEach(function(elem) {
var icon = document.createElement("div");
icon.className = "sort-icon";
icon.textContent = "vvv";
elem.insertBefore(icon, elem.firstChild);
elem.classList.remove("desc", "active");
elem.classList.add("asc", "inactive");
});
}
function toggle_sort_states(elem) {
//if active, toggle between asc and desc
if (elem.classList.contains('active')) {
elem.classList.toggle('asc');
elem.classList.toggle('desc');
}
//if inactive, reset all other functions and add ascending active
if (elem.classList.contains('inactive')) {
reset_sort_headers();
elem.classList.remove('inactive');
elem.classList.add('active');
}
}
function is_all_rows_hidden(value) {
return value.hidden == false;
}
function filter_table(elem) {
var outcome_att = "data-test-result";
var outcome = elem.getAttribute(outcome_att);
class_outcome = outcome + " results-table-row";
var outcome_rows = document.getElementsByClassName(class_outcome);
for(var i = 0; i < outcome_rows.length; i++){
outcome_rows[i].hidden = !elem.checked;
}
var rows = find_all('.results-table-row').filter(is_all_rows_hidden);
var all_rows_hidden = rows.length == 0 ? true : false;
var not_found_message = document.getElementById("not-found-message");
not_found_message.hidden = !all_rows_hidden;
}
</script>
<h1>send-test.html</h1>
<p>Report generated on 14-Jun-2018 at 14:20:27 by<a href="https://pypi.python.org/pypi/pytest-html"> pytest-html</a> v1.19.0</p>
<h2>Environment</h2>
<table id="environment">
<tr>
<td>Base URL</td>
<td><a href="http://localhost:1443" target="_blank">http://localhost:1443</a></td></tr>
<tr>
<td>Driver</td>
<td>Firefox</td></tr>
<tr>
<td>JAVA_HOME</td>
<td>/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home</td></tr>
<tr>
<td>Packages</td>
<td>{&apos;pytest&apos;: &apos;3.6.1&apos;, &apos;py&apos;: &apos;1.5.3&apos;, &apos;pluggy&apos;: &apos;0.6.0&apos;}</td></tr>
<tr>
<td>Platform</td>
<td>Darwin-17.5.0-x86_64-i386-64bit</td></tr>
<tr>
<td>Plugins</td>
<td>{&apos;xdist&apos;: &apos;1.22.2&apos;, &apos;variables&apos;: &apos;1.7.1&apos;, &apos;selenium&apos;: &apos;1.13.0&apos;, &apos;metadata&apos;: &apos;1.7.0&apos;, &apos;html&apos;: &apos;1.19.0&apos;, &apos;forked&apos;: &apos;0.2&apos;, &apos;base-url&apos;: &apos;1.4.1&apos;}</td></tr>
<tr>
<td>Python</td>
<td>3.6.5</td></tr></table>
<h2>Summary</h2>
<p>3 tests ran in 20.54 seconds. </p>
<p class="filter" hidden="true">(Un)check the boxes to filter the results.</p><input checked="true" class="filter" data-test-result="passed" hidden="true" name="filter_checkbox" onChange="filter_table(this)" type="checkbox"/><span class="passed">3 passed</span>, <input checked="true" class="filter" data-test-result="skipped" disabled="true" hidden="true" name="filter_checkbox" onChange="filter_table(this)" type="checkbox"/><span class="skipped">0 skipped</span>, <input checked="true" class="filter" data-test-result="failed" disabled="true" hidden="true" name="filter_checkbox" onChange="filter_table(this)" type="checkbox"/><span class="failed">0 failed</span>, <input checked="true" class="filter" data-test-result="error" disabled="true" hidden="true" name="filter_checkbox" onChange="filter_table(this)" type="checkbox"/><span class="error">0 errors</span>, <input checked="true" class="filter" data-test-result="xfailed" disabled="true" hidden="true" name="filter_checkbox" onChange="filter_table(this)" type="checkbox"/><span class="xfailed">0 expected failures</span>, <input checked="true" class="filter" data-test-result="xpassed" disabled="true" hidden="true" name="filter_checkbox" onChange="filter_table(this)" type="checkbox"/><span class="xpassed">0 unexpected passes</span>
<h2>Results</h2>
<table id="results-table">
<thead id="results-table-head">
<tr>
<th class="sortable result initial-sort" col="result">Result</th>
<th class="sortable" col="name">Test</th>
<th class="sortable numeric" col="duration">Duration</th>
<th>Links</th></tr>
<tr hidden="true" id="not-found-message">
<th colspan="4">No results found. Try to check the filters</th></tr></thead>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">test_download.py::test_download</td>
<td class="col-duration">0.00</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">test_progress.py::test_progress</td>
<td class="col-duration">0.00</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody>
<tbody class="passed results-table-row">
<tr>
<td class="col-result">Passed</td>
<td class="col-name">test_upload.py::test_upload</td>
<td class="col-duration">0.01</td>
<td class="col-links"></td></tr>
<tr>
<td class="extra" colspan="4">
<div class="empty log">No log output captured.</div></td></tr></tbody></table></body></html>

View file

@ -1,6 +0,0 @@
"""Test files regarding downloads."""
def test_download(download_file, download_location_dir, test_file):
"""Test downloaded file matches uploaded file."""
assert download_location_dir.ensure(test_file.name)

View file

@ -1,14 +0,0 @@
import pytest
from pages.desktop.base import Base
footer_links = ['mozilla', 'mozilla', 'about', 'legal', 'legal', 'cookies',
'report-infringement']
@pytest.mark.parametrize('i, name', enumerate(footer_links))
def test_legal_links(selenium, base_url, i, name):
"""Test links in footer load correct pages."""
page = Base(selenium, base_url).open()
page.footer.links[i].click()
assert name in selenium.current_url

View file

@ -1,6 +0,0 @@
"""Test files regarding the upload progress pages."""
def test_progress(upload_file):
"""Test progress icon shows while uploading."""
assert upload_file

View file

@ -1,6 +0,0 @@
"""Test files regarding uploading."""
def test_upload(upload_file):
"""Test file upload and creates URL."""
assert upload_file.file_url is not None

View file

@ -1,19 +0,0 @@
[tox]
envlist = integration-tests, flake8
skipsdist = True
[testenv]
skip_install = True
deps = -rrequirements.txt
commands =
pytest -v --verify-base-url --base-url {env:BASE_URL:http://web:1443} --driver Remote --capability browserName firefox --host selenium --html=/coverage/send-test.html --self-contained-html {posargs}
[testenv:flake8]
commands =
flake8 {posargs:.}
[flake8]
exclude = .eggs,.tox,docs,node_modules
[pytest]
sensitive_url = mozilla\.(com|org)

34
test/testServer.js Normal file
View file

@ -0,0 +1,34 @@
let server = null;
module.exports = {
onPrepare: function() {
return new Promise(function(resolve) {
const webpack = require('webpack');
const middleware = require('webpack-dev-middleware');
const express = require('express');
const expressWs = require('express-ws');
const assets = require('../common/assets');
const locales = require('../common/locales');
const routes = require('../server/routes');
const tests = require('./frontend/routes');
const app = express();
const config = require('../webpack.config');
const wpm = middleware(webpack(config(null, { mode: 'development' })), {
logLevel: 'silent'
});
app.use(wpm);
assets.setMiddleware(wpm);
locales.setMiddleware(wpm);
expressWs(app, null, { perMessageDeflate: false });
app.ws('/api/ws', require('../server/routes/ws'));
routes(app);
tests(app);
wpm.waitUntilValid(() => {
server = app.listen(8000, resolve);
});
});
},
onComplete: function() {
server.close();
}
};

View file

@ -0,0 +1,18 @@
// eslint-disable-next-line node/no-extraneous-require
const ip = require('ip');
const path = require('path');
const common = require('./wdio.common.conf');
/*/
Config for running selenium from a circleci docker container against localhost
/*/
exports.config = Object.assign({}, common.config, {
baseUrl: `http://${ip.address()}:8000`,
exclude: [path.join(__dirname, './integration/download-tests.js')],
maxInstances: 1,
bail: 1,
services: [require('./testServer')]
});

48
test/wdio.common.conf.js Normal file
View file

@ -0,0 +1,48 @@
const path = require('path');
const mkdirp = require('mkdirp');
const rimraf = require('rimraf');
const dir = path.join(__dirname, 'integration', 'downloads');
mkdirp.sync(dir);
rimraf.sync(`${dir}${path.sep}*`);
exports.config = {
specs: [path.join(__dirname, './integration/**/*-tests.js')],
exclude: [],
maxInstances: 10,
capabilities: [
{
browserName: 'firefox',
'moz:firefoxOptions': {
log: { level: 'trace' },
prefs: {
'browser.download.panel.shown': false,
'browser.helperApps.neverAsk.openFile': 'text/plain',
'browser.helperApps.neverAsk.saveToDisk': 'text/plain',
'browser.download.folderList': 2,
'browser.download.dir': dir
}
}
}
],
pageLoadStrategy: 'normal',
watch: false,
async: true,
logLevel: 'error',
coloredLogs: true,
deprecationWarnings: true,
bail: 0,
screenshotOnReject: false,
baseUrl: 'http://localhost:8000',
waitforTimeout: 20000,
connectionRetryTimeout: 90000,
connectionRetryCount: 3,
services: ['firefox-profile'],
framework: 'mocha',
reporters: ['dot', 'spec'],
mochaOpts: {
ui: 'bdd',
timeout: 30000,
retries: 1
}
};

28
test/wdio.docker.conf.js Normal file
View file

@ -0,0 +1,28 @@
// eslint-disable-next-line node/no-extraneous-require
const ip = require('ip');
const common = require('./wdio.common.conf');
const dir =
common.config.capabilities[0]['moz:firefoxOptions'].prefs[
'browser.download.dir'
];
/*/
Config for running selenium in a new docker container against localhost
/*/
exports.config = Object.assign({}, common.config, {
baseUrl: `http://${ip.address()}:8000`,
maxInstances: 1,
services: ['docker', require('./testServer')],
dockerOptions: {
image: 'selenium/standalone-firefox',
healthCheck: 'http://localhost:4444',
options: {
p: ['4444:4444'],
mount: `type=bind,source=${dir},destination=${dir},consistency=delegated`,
shmSize: '2g'
}
}
});

16
test/wdio.local.conf.js Normal file
View file

@ -0,0 +1,16 @@
// eslint-disable-next-line node/no-extraneous-require
const ip = require('ip');
const common = require('./wdio.common.conf');
/*/
Config for running selenium against localhost
/*/
exports.config = Object.assign({}, common.config, {
baseUrl: `http://${ip.address()}:8000`,
maxInstances: 1,
bail: 1,
services: [require('./testServer')]
});

View file

@ -0,0 +1,29 @@
const common = require('./wdio.common.conf');
const path = require('path');
/*/
Config for running saucelabs against a hosted server
/*/
exports.config = Object.assign({}, common.config, {
baseUrl: process.env.TEST_SERVER || 'https://send.dev.mozaws.net',
exclude: [
// the /test endpoint only exists on localhost
path.join(__dirname, './integration/unit-tests.js'),
// we don't have access to the fs in this context
path.join(__dirname, './integration/download-tests.js')
],
capabilities: [
{ browserName: 'firefox' },
{ browserName: 'chrome' },
{ browserName: 'MicrosoftEdge' },
{
browserName: 'safari'
}
],
services: ['sauce'],
user: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY
});

View file

@ -0,0 +1,30 @@
const common = require('./wdio.common.conf');
const path = require('path');
/*/
Config for running saucelabs against localhost
/*/
exports.config = Object.assign({}, common.config, {
maxInstances: 2,
exclude: [path.join(__dirname, './integration/download-tests.js')],
capabilities: [
{ browserName: 'firefox' },
{ browserName: 'chrome' },
{ browserName: 'MicrosoftEdge' },
{
browserName: 'safari'
}
],
services: ['sauce', require('./testServer')],
sauceConnect: true,
sauceConnectOpts: {
// uncomment to debug
// logfile: __dirname + '/sc.log',
// verbose: true
},
user: process.env.SAUCE_USERNAME,
key: process.env.SAUCE_ACCESS_KEY
});