Commit 446f3787 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch 'karma-headless-chrome-redux' into 'master'

Replace PhantomJS with Chrome in Karma test runner (2nd attempt)

Closes #33633

See merge request !12144
parents c676e79e 132be812
...@@ -441,19 +441,23 @@ gitlab:assets:compile: ...@@ -441,19 +441,23 @@ gitlab:assets:compile:
- webpack-report/ - webpack-report/
karma: karma:
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.3-golang-1.8-git-2.7-chrome-59.0-node-7.1-postgresql-9.6"
stage: test stage: test
<<: *use-pg <<: *use-pg
<<: *dedicated-runner <<: *dedicated-runner
<<: *except-docs <<: *except-docs
variables: variables:
BABEL_ENV: "coverage" BABEL_ENV: "coverage"
CHROME_LOG_FILE: "chrome_debug.log"
script: script:
- bundle exec rake karma - bundle exec rake karma
coverage: '/^Statements *: (\d+\.\d+%)/' coverage: '/^Statements *: (\d+\.\d+%)/'
artifacts: artifacts:
name: coverage-javascript name: coverage-javascript
expire_in: 31d expire_in: 31d
when: always
paths: paths:
- chrome_debug.log
- coverage-javascript/ - coverage-javascript/
codeclimate: codeclimate:
......
...@@ -77,7 +77,7 @@ const Api = { ...@@ -77,7 +77,7 @@ const Api = {
dataType: 'json', dataType: 'json',
}) })
.done(label => callback(label)) .done(label => callback(label))
.error(message => callback(message.responseJSON)); .fail(message => callback(message.responseJSON));
}, },
// Return group projects list. Filtered by query // Return group projects list. Filtered by query
...@@ -134,7 +134,7 @@ const Api = { ...@@ -134,7 +134,7 @@ const Api = {
dataType: 'json', dataType: 'json',
}) })
.done(file => callback(null, file)) .done(file => callback(null, file))
.error(callback); .fail(callback);
}, },
users(query, options) { users(query, options) {
......
---
title: Replace PhantomJS with headless Chrome for karma test suite
merge_request: 12036
author:
...@@ -21,7 +21,18 @@ module.exports = function(config) { ...@@ -21,7 +21,18 @@ module.exports = function(config) {
var karmaConfig = { var karmaConfig = {
basePath: ROOT_PATH, basePath: ROOT_PATH,
browsers: ['PhantomJS'], browsers: ['ChromeHeadlessCustom'],
customLaunchers: {
ChromeHeadlessCustom: {
base: 'ChromeHeadless',
displayName: 'Chrome',
flags: [
// chrome cannot run in sandboxed mode inside a docker container unless it is run with
// escalated kernel privileges (e.g. docker run --cap-add=CAP_SYS_ADMIN)
'--no-sandbox',
],
}
},
frameworks: ['jasmine'], frameworks: ['jasmine'],
files: [ files: [
{ pattern: 'spec/javascripts/test_bundle.js', watched: false }, { pattern: 'spec/javascripts/test_bundle.js', watched: false },
...@@ -45,5 +56,14 @@ module.exports = function(config) { ...@@ -45,5 +56,14 @@ module.exports = function(config) {
}; };
} }
if (process.env.DEBUG) {
karmaConfig.logLevel = config.LOG_DEBUG;
process.env.CHROME_LOG_FILE = process.env.CHROME_LOG_FILE || 'chrome_debug.log';
}
if (process.env.CHROME_LOG_FILE) {
karmaConfig.customLaunchers.ChromeHeadlessCustom.flags.push('--enable-logging', '--v=1');
}
config.set(karmaConfig); config.set(karmaConfig);
}; };
import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
(() => { (() => {
// TODO: remove this hack!
// PhantomJS causes spyOn to panic because replaceState isn't "writable"
let phantomjs;
try {
phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable;
} catch (err) {
phantomjs = false;
}
describe('Linked Tabs', () => { describe('Linked Tabs', () => {
preloadFixtures('static/linked_tabs.html.raw'); preloadFixtures('static/linked_tabs.html.raw');
...@@ -19,9 +10,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; ...@@ -19,9 +10,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
describe('when is initialized', () => { describe('when is initialized', () => {
beforeEach(() => { beforeEach(() => {
if (!phantomjs) { spyOn(window.history, 'replaceState').and.callFake(function () {});
spyOn(window.history, 'replaceState').and.callFake(function () {});
}
}); });
it('should activate the tab correspondent to the given action', () => { it('should activate the tab correspondent to the given action', () => {
...@@ -47,7 +36,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs'; ...@@ -47,7 +36,7 @@ import LinkedTabs from '~/lib/utils/bootstrap_linked_tabs';
describe('on click', () => { describe('on click', () => {
it('should change the url according to the clicked tab', () => { it('should change the url according to the clicked tab', () => {
const historySpy = !phantomjs && spyOn(history, 'replaceState').and.callFake(() => {}); const historySpy = spyOn(history, 'replaceState').and.callFake(() => {});
const linkedTabs = new LinkedTabs({ const linkedTabs = new LinkedTabs({
action: 'show', action: 'show',
......
...@@ -5,15 +5,6 @@ import '~/pager'; ...@@ -5,15 +5,6 @@ import '~/pager';
import '~/commits'; import '~/commits';
(() => { (() => {
// TODO: remove this hack!
// PhantomJS causes spyOn to panic because replaceState isn't "writable"
let phantomjs;
try {
phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable;
} catch (err) {
phantomjs = false;
}
describe('Commits List', () => { describe('Commits List', () => {
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
...@@ -61,9 +52,7 @@ import '~/commits'; ...@@ -61,9 +52,7 @@ import '~/commits';
CommitsList.init(25); CommitsList.init(25);
CommitsList.searchField.val(''); CommitsList.searchField.val('');
if (!phantomjs) { spyOn(history, 'replaceState').and.stub();
spyOn(history, 'replaceState').and.stub();
}
ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => { ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => {
req.success({ req.success({
data: '<li>Result</li>', data: '<li>Result</li>',
......
...@@ -126,7 +126,7 @@ describe('Issuable output', () => { ...@@ -126,7 +126,7 @@ describe('Issuable output', () => {
describe('updateIssuable', () => { describe('updateIssuable', () => {
it('fetches new data after update', (done) => { it('fetches new data after update', (done) => {
spyOn(vm.service, 'getData'); spyOn(vm.service, 'getData').and.callThrough();
spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => { spyOn(vm.service, 'updateIssuable').and.callFake(() => new Promise((resolve) => {
resolve({ resolve({
json() { json() {
......
...@@ -12,15 +12,6 @@ import '~/notes'; ...@@ -12,15 +12,6 @@ import '~/notes';
import 'vendor/jquery.scrollTo'; import 'vendor/jquery.scrollTo';
(function () { (function () {
// TODO: remove this hack!
// PhantomJS causes spyOn to panic because replaceState isn't "writable"
var phantomjs;
try {
phantomjs = !Object.getOwnPropertyDescriptor(window.history, 'replaceState').writable;
} catch (err) {
phantomjs = false;
}
describe('MergeRequestTabs', function () { describe('MergeRequestTabs', function () {
var stubLocation = {}; var stubLocation = {};
var setLocation = function (stubs) { var setLocation = function (stubs) {
...@@ -37,11 +28,9 @@ import 'vendor/jquery.scrollTo'; ...@@ -37,11 +28,9 @@ import 'vendor/jquery.scrollTo';
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation }); this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
setLocation(); setLocation();
if (!phantomjs) { this.spies = {
this.spies = { history: spyOn(window.history, 'replaceState').and.callFake(function () {})
history: spyOn(window.history, 'replaceState').and.callFake(function () {}) };
};
}
}); });
afterEach(function () { afterEach(function () {
...@@ -208,11 +197,9 @@ import 'vendor/jquery.scrollTo'; ...@@ -208,11 +197,9 @@ import 'vendor/jquery.scrollTo';
pathname: '/foo/bar/merge_requests/1' pathname: '/foo/bar/merge_requests/1'
}); });
newState = this.subject('commits'); newState = this.subject('commits');
if (!phantomjs) { expect(this.spies.history).toHaveBeenCalledWith({
expect(this.spies.history).toHaveBeenCalledWith({ url: newState
url: newState }, document.title, newState);
}, document.title, newState);
}
}); });
it('treats "show" like "notes"', function () { it('treats "show" like "notes"', function () {
......
...@@ -95,7 +95,7 @@ describe('Interval Pattern Input Component', function () { ...@@ -95,7 +95,7 @@ describe('Interval Pattern Input Component', function () {
describe('User Actions', function () { describe('User Actions', function () {
beforeEach(function () { beforeEach(function () {
// For an unknown reason, Phantom.js doesn't trigger click events // For an unknown reason, some browsers do not propagate click events
// on radio buttons in a way Vue can register. So, we have to mount // on radio buttons in a way Vue can register. So, we have to mount
// to a fixture. // to a fixture.
setFixtures('<div id="my-mount"></div>'); setFixtures('<div id="my-mount"></div>');
......
import Pipelines from '~/pipelines'; import Pipelines from '~/pipelines';
// Fix for phantomJS
if (!Element.prototype.matches && Element.prototype.webkitMatchesSelector) {
Element.prototype.matches = Element.prototype.webkitMatchesSelector;
}
describe('Pipelines', () => { describe('Pipelines', () => {
preloadFixtures('static/pipeline_graph.html.raw'); preloadFixtures('static/pipeline_graph.html.raw');
......
...@@ -87,7 +87,7 @@ describe('Header CI Component', () => { ...@@ -87,7 +87,7 @@ describe('Header CI Component', () => {
vm.actions[0].isLoading = true; vm.actions[0].isLoading = true;
Vue.nextTick(() => { Vue.nextTick(() => {
expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toEqual(''); expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy();
done(); done();
}); });
}); });
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment