-
Notifications
You must be signed in to change notification settings - Fork 9.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Stop the trace when multiple tabs are found. #639
Conversation
let activeServiceWorkers = []; | ||
registrations | ||
.filter(reg => !reg.isDeleted) | ||
.filter(reg => parseURL(reg.scopeURL).hostname === parseURL(pageUrl).hostname) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to check the origin is the same, not just the hostname
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh, I think @paulirish had something about using just hostname to do some limited redirect handling. One issue is port number...e.g. our smoketest on localhost:whatever
might alert on this even though one of them has a SW and one doesn't
191a6c2
to
d05bcfb
Compare
@@ -145,7 +145,7 @@ lighthouse(url, flags, config) | |||
if (err.code === 'ECONNREFUSED') { | |||
console.error('Unable to connect to Chrome. Please run Chrome w/ debugging port 9222 open:'); | |||
console.error(' npm explore -g lighthouse -- npm run chrome'); | |||
} else { | |||
} else if (err) { | |||
console.error('Runtime error encountered:', err); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not a bad idea. thanks
a811960
to
9ddb408
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some quick nits
return new Promise((resolve, reject) => { | ||
this.queryCurrentTab_().then(currentTab => { | ||
chrome.debugger.getTargets(targets => { | ||
const tab = targets.find(tab => tab.tabId === currentTab.id); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe rename tab
to target
to keep things clearer?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes ^^. for all three on this line.
const target = targets.find(target => target.tabId === currentTab.id);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
return Promise.all([getRegistrations, getVersions, getActiveTabId]).then(res => { | ||
const registrations = res[0].registrations; | ||
const versions = res[1].versions; | ||
const activePageId = res[2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
still should be activeTabId
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
|
||
const multipleTabsAttached = ver.controlledClients.length > 1; | ||
const oneInactiveTabIsAttached = ver.controlledClients.length === 1 && | ||
!ver.controlledClients.find(sw => sw === activePageId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
rename sw
to client
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
message = 'You probably have DevTools open.' + | ||
' Close DevTools to use lighthouse'; | ||
} | ||
if (message.toLowerCase().startsWith('unable to kill')) { | ||
message = 'You probably have multiple tabs open.' + |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You probably have multiple tabs open to the same origin
or something like that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
Updated |
6dc888a
to
a58fef2
Compare
updated, sorry for the many reviews! |
|
||
let swHasMoreThanOneClient = false; | ||
registrations | ||
.filter(reg => !reg.isDeleted) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nuke this isDeleted filter. Even if it's deleted (aka unregistered) it can come back from the dead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow didn't know that
}); | ||
|
||
if (swHasMoreThanOneClient) { | ||
throw new Error( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we'll need to call driver.disconnect
around now, so we dont keep the debugger attached.
Also, Is there any other cleanup we need to do?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will check if anything else needs to be done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only needed to disconnect the driver. Couldn't find anything else. I will have a look at what happens when we close the trace in between.. (shouldn't be part of this PR)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking good, a few more remaining things I've added. Sorry we were slow this week, lots of stuff going on.
I'll leave checkForMultipleTabsAttached
for @paulirish to check, he's the one who's had his head in there :)
.catch(err => { | ||
if (err.code === 'ECONNREFUSED') { | ||
showConnectionError(); | ||
} else if (err.message.toLowerCase().includes('multiple tabs')) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want this down in handleError()
} | ||
|
||
/** | ||
* Checks all serviceworkes and see if any duplications are running |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
service workers
@@ -44,7 +44,7 @@ const Config = require('./config/config'); | |||
*/ | |||
|
|||
module.exports = function(url, flags, configJSON) { | |||
return new Promise((resolve, reject) => { | |||
return (new Promise((resolve, reject) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason to add these parens here?
@@ -62,7 +62,7 @@ module.exports = function(url, flags, configJSON) { | |||
|
|||
// kick off a lighthouse run | |||
resolve(Runner.run(driver, {url, flags, config})); | |||
}); | |||
})); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
@@ -251,8 +260,7 @@ class GatherRunner { | |||
// We dont need to hold up the reporting for the reload/disconnect, | |||
// so we will not return a promise in here. | |||
driver.reloadForCleanStateIfNeeded(options).then(_ => { | |||
log.log('status', 'Disconnecting from browser...'); | |||
driver.disconnect(); | |||
GatherRunner.disposeDriver(driver); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe move the log message back here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure? I feel it's ok in dispose because of another source runs dispose we still want to show the log disconnecting because it's tightly coupled to disconnect?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how you have it is probably fine
}) | ||
// cleanup on error | ||
.catch(err => { | ||
GatherRunner.disposeDriver(driver); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that way you can have an error specific log here. Even as simple as `log.log('status', 'Disconnecting from browser after error...'); or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice job adding this here. We should have been doing this for other errors already
@@ -91,9 +92,17 @@ class GatherRunner { | |||
}).then(_ => { | |||
// Clears storage for origin. | |||
return driver.clearDataForOrigin(options.url); | |||
}).then(_ => { | |||
// Check Service Workers running | |||
return driver.checkForMultipleTabsAttached(options.url); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like setupDriver
has a weird order. Feel free to punt to a followup PR, but I feel like the multiple tabs check should come before clearingDataForOrigin
, for instance (why bother if the SW clear isn't going to work anyways?). Not sure of optimal order, though.
}); | ||
}); | ||
}); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you want to switch the outer Promise and this.queryCurrentTab_
here, to put this.queryCurrentTab_()
into the promise chain so any rejections in it will be handled. Something like:
return this.queryCurrentTab_().then(currentTab => {
return new Promise((resolve, reject) => {
chrome.debugger.getTargets(targets => {
const target = targets.find(target => target.tabId === currentTab.id);
if (!target) {
reject(new Error('We can\'t find a target id.'));
}
resolve(target.id);
});
});
});
oh, he already approved |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a few more comments on the tests :)
@@ -80,4 +83,123 @@ describe('Browser Driver', () => { | |||
assert.notEqual(opts.url, req1.url, 'opts.url changed after the redirects'); | |||
assert.equal(opts.url, req3.url, 'opts.url matches the last redirect'); | |||
}); | |||
|
|||
it('will fail when multiple tabs are found with the same active serviceworker', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be helpful to surround these tests with a nested
describe('Multiple tab check', () => {
to make it clear what the it
in these are, since these are just tests for checkForMultipleTabsAttached
, not end to end tests.
.then(_ => assert.ok(true), _ => assert.ok(false)); | ||
}); | ||
|
||
it('will succeed when old sw are deleted', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this test still what you want given what @paulirish said about deleted service workers?
.then(_ => assert.ok(true), _ => assert.ok(false)); | ||
}); | ||
|
||
it('will succeed when only one sw loaded', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/sw/service worker
}); | ||
|
||
function createOnceStub(events) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move these to the top of the file with the other helper functions
@brendankenny could you look over this :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey Ward. Somehow I missed your update commits and thought this PR was just chilling. Thanks for pinging me and staying on this :) Looking good, just some last comments.
@@ -196,6 +196,13 @@ function lighthouseRun(addresses) { | |||
} | |||
|
|||
return lighthouseRun(addresses); | |||
}) | |||
.catch(err => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unfortunately I can't remember at all now, but why is this catch
necessary and errors won't be handled by handleError
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prolly just a mistake
return new Promise(resolve => { | ||
this.once('ServiceWorker.workerRegistrationUpdated', data => { | ||
this.sendCommand('ServiceWorker.disable'); | ||
resolve(data); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need to add what's been added to getServiceWorkerVersions
to handle promise rejections.
this.sendCommand('ServiceWorker.disable')
.then(_ => resolve(data), reject);
and this.sendCommand('ServiceWorker.enable').catch(reject);
@@ -90,11 +91,19 @@ class GatherRunner { | |||
.then(_ => { | |||
return driver.cleanAndDisableBrowserCaches(); | |||
}).then(_ => { | |||
// Check if multiple tabs of origin are open |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I now think this check definitely be moved up first. No point in getting started if it's not going to work out. Maybe just:
return driver.checkForMultipleTabsAttached(options.url)
.then(_ => {
// Enable emulation if required.
return options.flags.mobile && driver.beginEmulation();
}).then(_ => {
return driver.cleanAndDisableBrowserCaches();
}).then(_ => {
// Clears storage for origin.
return driver.clearDataForOrigin(options.url);
});
?
@@ -251,8 +260,7 @@ class GatherRunner { | |||
// We dont need to hold up the reporting for the reload/disconnect, | |||
// so we will not return a promise in here. | |||
driver.reloadForCleanStateIfNeeded(options).then(_ => { | |||
log.log('status', 'Disconnecting from browser...'); | |||
driver.disconnect(); | |||
GatherRunner.disposeDriver(driver); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how you have it is probably fine
}) | ||
.forEach(reg => { | ||
// Check if any of the service workers are the same (registration id) | ||
// Check if the controlledClients are bigger than 1 and it's not the active tab |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move these comments down to each step
|
||
const multipleTabsAttached = ver.controlledClients.length > 1; | ||
const oneInactiveTabIsAttached = ver.controlledClients.length === 1 && | ||
!ver.controlledClients.find(client => client === activeTabId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: since you know it's length 1, clearer to just write this line as ver.controlledClients[0] !== activeTabId;
?
} | ||
|
||
/** | ||
* Checks all service workers and see if any duplications are running |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
something like Rejects if any open tabs would share a service worker with the target URL.
?
hmm, smoke test is broken, saying it's hitting |
* @return {!Promise<string>} | ||
*/ | ||
getCurrentTabId() { | ||
return Promise.reject(new Error('Not implemented')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
looks like the issue is here. Driver
no longer extends ExtensionConnection
or CriConnection
, so I believe driver's getCurrentTabId
needs to call this._connection.getCurrentTabId()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:)
); | ||
} | ||
|
||
return res; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to return anything here
log.log('status', 'Disconnecting from browser...'); | ||
return driver.disconnect(); | ||
}).then(_ => { | ||
// We dont need to hold up the reporting for the reload/disconnect, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this section doesn't exist anymore after #816. I think you just want
.then(_ => GatherRunner.disposeDriver(driver))
.then(_ => {
// Collate all the gatherer results.
...
.then(_ => driver.enableRuntimeEvents()) | ||
.then(_ => driver.cleanAndDisableBrowserCaches()) | ||
.then(_ => driver.clearDataForOrigin(options.url)); | ||
} | ||
|
||
static disposeDriver(driver) { | ||
log.log('status', 'Disconnecting from browser...'); | ||
driver.disconnect(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should copy the
// We dont need to hold up the reporting for the reload/disconnect,
// so we will not return a promise in here.
comment up here so it's clear why driver.disconnect();
isn't being returned
@brendankenny fixed latest nits :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks great! 🎊 🎉 🎈 🍰
Fixes #601
I check if multiple service workers got found and it throws an error. I check in the cli and the extension on this error and show the a good message.