-
Notifications
You must be signed in to change notification settings - Fork 228
Description
Hello! I'm looking for some help. I've been spinning my wheels at this for a while. I am newcomer to Electron app development and am starting to get very lower level.
I'm trying to set up a development phase CI pipeline for my team using Jenkins (CI platform my company uses) with Docker. Part of this includes a quality gate with our unit tests and also our Spectron E2E tests.
So I'm trying to run our existing Spectron suite automatically headless within a Docker container on Jenkins. For the base image doing this, I've followed the Provided Docker images in the Electron Build documentation and am building my Docker container from electronuserland/builder:wine-chrome
.
Right now, I am trying to start simple by just testing start of the Spectron Application and launching a window
MY SPEC:
import { appUtil } from '../utils/app-utils';
fdescribe('Application launch', () => {
jasmine.DEFAULT_TIMEOUT_INTERVAL = 20000;
const app = appUtil.app;
console.log('/---------/ SPECTRON APP AFTER INIT /---------/')
console.log(app);
beforeAll(async () => {
await appUtil.startApp();
//await app.client.waitUntilWindowLoaded();
console.log('/---------/ SPECTRON APP AFTER START /---------/')
console.log(app);
});
afterAll(async () =>
await appUtil.stopApp()
);
it('Should show a window', async () => {
console.log('/---------/ SPECTRON APP CLIENT /---------/');
console.log(app.client);
console.log('/---------/ APP CLIENT STATUS /---------/');
console.log(await app.client.status());
console.log('/---------/ APP CLIENT DESIRED CAPABILITIES CHROME OPTIONS /---------/');
console.log(await app.client.desiredCapabilities.chromeOptions);
let count = await app.client.getWindowCount();
expect(count).toBe(1);
});
});
APPUTIL (where we have Spectron Application initialized):
import * as electron from 'electron';
import { Application } from 'spectron';
import { join } from 'path';
export class AppUtil {
app = new Application({
path: '' + electron,
args: [join(__dirname, '../../../..')],
chromeDriverLogPath: '../../chromeDriver.log',
webdriverLogPath: '../../webdriver.log'
});
public async startApp(): Promise<Application> {
if (this.appIsRunning()) {
const result = await this.waitForAppClosure();
if(!result){
throw new Error("Application failed to close within the allotted timeout");
};
};
await this.app.start();
this.app.browserWindow.focus();
this.app.browserWindow.setAlwaysOnTop(true);
return this.app;
}
public async stopApp(): Promise<Application> {
if (this.appIsRunning()) {
this.app.stop();
this.app.mainProcess.abort()
};
//allow time for terminal windows to close
await sleep(1000);
return Promise.resolve(this.app);
}
private appIsRunning(): boolean {
return !!(this.app && this.app.isRunning());
}
/**
* Wait for the app to close for a max of 5 secs
*/
private async waitForAppClosure(): Promise<boolean> {
let count = 0;
while(this.appIsRunning() && (count < 5)){
await sleep(1000);
count++;
};
return !this.appIsRunning();
}
}
export const appUtil = new AppUtil();
/**
* Sleeps for a specified amount of time
*/
export async function sleep(time: number): Promise<void> {
return new Promise(r => setTimeout(r, time));
}
When I run these, I get the following result in Jenkins output:
Failures:
1) Application launch Should show a window
Message:
Error: A session id is required for this command but wasn't found in the response payload
Stack:
error properties: Object({ type: 'NoSessionIdError' })
Error: A session id is required for this command but wasn't found in the response payload
at windowHandles() - application.js:274:17
Suite error: Application launch
Message:
Error: Client initialization failed after 10 attempts: RuntimeError Client initialization failed after 10 attempts:
Stack:
error properties: Object({ details: undefined, type: 'RuntimeError', seleniumStack: Object({ type: 'UnknownError', message: 'An unknown server-side error occurred while processing the command.', orgStatusMessage: 'unknown error: Chrome failed to start: exited abnormally.
(unknown error: DevToolsActivePort file doesn't exist)
(The process started from chrome location /root/repo/apps/myapp/node_modules/spectron/lib/launcher.js is no longer running, so ChromeDriver is assuming that Chrome has crashed.)
(Driver info: chromedriver=80.0.3987.86 (4c2763461dfdcdda46516e2a9060c22a3c1a2525-refs/branch-heads/3987@{#796}),platform=Linux 3.10.0-1127.19.1.el7.x86_64 x86_64)' }) })
Error: unknown error: Chrome failed to start: exited abnormally.
at <Jasmine>
at new RuntimeError (/root/repo/apps/myapp/node_modules/spectron/node_modules/webdriverio/build/lib/utils/ErrorHandler.js:143:12)
at Request._callback (/root/repo/apps/myapp/node_modules/spectron/node_modules/webdriverio/build/lib/utils/RequestHandler.js:318:39)
at Request.self.callback (/root/repo/apps/myapp/node_modules/request/request.js:185:22)
at Request.emit (events.js:376:20)
at Request.emit (domain.js:470:12)
at Request.<anonymous> (/root/repo/apps/myapp/node_modules/request/request.js:1154:10)
at Request.emit (events.js:376:20)
at Request.emit (domain.js:470:12)
at IncomingMessage.<anonymous> (/root/repo/apps/myapp/node_modules/request/request.js:1076:12)
at Object.onceWrapper (events.js:482:28)
Ran 1 of 219 specs
1 spec, 2 failures
Finished in 4.578 seconds
It seems like the highlight here is DevToolsActivePort file doesn't exist
.
When I've googled around about this, many people discuss passing arguments to Chrome Driver.
Passing in Chrome Driver Args
I noticed Spectron's README the Application API shows chromeDriverArgs
. So I've been passing an array of the arguments into the Application constructor like so:
app = new Application({
path: '' + electron,
args: [join(__dirname, '../../../..')],
chromeDriverLogPath: '../../chromeDriver.log',
webdriverLogPath: '../../webdriver.log',
chromeDriverArgs: [
'--no-sandbox',
'--disable-dev-shm-usage',
'--headless',
'start-maximized',
'disable-infobars',
'--disable-gpu',
'--window-size=1920,1080'
]
});
I've played around with passing various combinations of these arguments:
'--no-sandbox',
'--disable-dev-shm-usage',
'--headless',
'--remote-debugging-port=9222',
'--user-data-dir=/home/ubuntujenkins/userData',
'start-maximized',
'disable-infobars',
'--disable-gpu',
'--window-size=1920,1080'
But still, no success. Only '--remote-debugging-port=9222' and '--user-data-dir=/home/ubuntujenkins/userData' makes the ``DevToolsActivePort file doesn't existerror disappear, but then it's replaced by
chrome unreachable`.
Xvfb Configuration?
I'm also unsure if maybe this can relate to an Xvfb misconfiguration?
I've tried:
- running Xvfb directly... adding sleep after like I've seen in some examples:
stage('test e2e') {
steps {
setupXvfbVariablesAndE2ETest()
}
}
//...
void setupXvfbVariablesAndE2ETest() {
// Similar to Travis CI example here: https://www.electronjs.org/docs/tutorial/testing-on-headless-ci
runCommand('Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 '
+ '& export DISPLAY=:99.0 '
+ '&& sleep 3 ' //give xvfb somem time to start
+ '&& npm run test-e2e')
}
- using xvfb-run... in my package.json:
"test-xvfb": "xvfb-run -al --error-file='./xvfb-error.log' --server-args='-screen 0, 1920x1080x24' sleep 5 && npm run test-e2e"
- using xvfb-maybe
"test-xvfb": "xvfb-maybe -al --error-file='./xvfb-error.log' --server-args='-screen 0, 1920x1080x24' -- npm run test-e2e
or more simply, I also tried:
"test-xvfb": "xvfb-maybe npm run test-e2e
- trying to run Xvfb and set DISPLAY environment variable in Dockerfile and/or Jenkinsfile before running
npm run test-e2e
:
- a line in Dockerfile:
RUN Xvfb :99 -screen 0 1024x768x24 > /dev/null 2>&1 & export DISPLAY=':99.0'
- in Jenkinsfile:
have tried running shell commands in this line:
sh 'Xvfb :99 -screen 0 1920x1080x24 > /dev/null 2>&1 && sleep 5 && npm run test-e2e'
also tried setting environment variable first:
pipeline {
environment {
DISPLAY = ':99.0'
}
//...
- try running Xvfb in a Docker entrypoint:
FROM electronuserland/builder:wine-chrome
ENV DISPLAY=:99
ADD docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
ENTRYPOINT [ "/docker-entrypoint.sh" ]
with
#!/usr/bin/env bash
echo "Starting Xvfb"
Xvfb :99 -ac &
sleep 5
echo "Executing command $@"
export DISPLAY=:99
exec "$@"
Lastly, I noticed the official Electron documentation mentions a plugin for Jenkins.
Is it possible on Jenkins, installing and configuring that plugin is necessary for Xvfb to behave as expected, even on a Docker container?
None of what I'm trying seems to be working. If it appears obvious to anyone that I am missing something, I'd be delighted if you let me know! I can also post some logs if requested (I have to scrub proprietary data references first).
Thanks so much!