fix: disable restored mocks#10413
Conversation
| const networkInterception = new NetworkInterception(url, filterOptions, browser) | ||
| SESSION_MOCKS[handle].add(networkInterception as Interception) | ||
|
|
||
| networkInterception.remove = () => { |
There was a problem hiding this comment.
couldn't figure out how to name it properly, because there are clear and restore
I thought about replacing clear/restore with this, but that would be a breaking change.
And yes, defining method like this does not look nice, but i coulnd't figure out a better alternative, because:
- you need
networkInterceptionin order to remove it from set - you need
SESSION_MOCKS, to know, where to delete from - you need
handle, to know where to exactly delete from - you need this
client: CDPSessionto disable fetch domain,
and accessing all of those from DevtoolsInterception hurts.
Let me know, if you could think of any better way to do it.
There was a problem hiding this comment.
I agree, this is not the right place to define this command in and we should find ways to put it in NetworkInterception.
you need networkInterception in order to remove it from set
If you define this method in the NetworkInterception class it is accessible through this.
you need SESSION_MOCKS, to know, where to delete from
SESSION_MOCKS then needs to be defined somewhere where mock.ts and the file where NetworkInterception is defined can import it from without having circular dependencies.
you need handle, to know where to exactly delete from
make remove async and get it via const handle = await browser.getWindowHandle()
you need this client: CDPSession to disable fetch domain,
You have access to the browser variable so you can get that variable too.
There was a problem hiding this comment.
The method mockRestore is meant to do what you want to do with remove .. can we just make sure the restore method ensures that nor memory is hogged?
There was a problem hiding this comment.
This is an issue that should be addressed but I am not happy with the current approach
That's why i converted it to draft: i found a better way to do it
There was a problem hiding this comment.
You have access to the browser variable so you can get that variable too
i tried, and that's what i discovered: you can get puppeteer through browser, but not client: CDPSession, because it would be different client, and we need original one. Anyway, it's manageable
There was a problem hiding this comment.
The method
mockRestoreis meant to do what you want to do withremove.. can we just make sure the restore method ensures that nor memory is hogged?
There is also a moment to consider. There are two options:
restorealso sendsFetch.disable, if there are no other mocks (ifrestoremeans "i dont need this anymore". In this case we would have to enable fetch domain again after somerespond/abort)restoredoes not disable fetch domain, even if there are no other mocks. (if you plan to userespond/abortafter it was restored. In this case we could just ensure this mock presents inSESSION_MOCKS)
First option also makes restore async for devtools and breaks existing "respond / abort after restore" cases, so i think about implementing second approach, but at first i need to clarify: what happens if you try to "respond / abort after restore" with saucelabs? Will it work just like mock wasn't restored?
There was a problem hiding this comment.
We have designed the API to be as close as possible to the Jest Mock API so I would say the first should be more appropriate. I feel honestly less worried about the Sauce Labs integration. I was working there at a time I build this and don't have any usage numbers. At the end of the day this API should support what will be supported in WebDriver Bidi because Sauce eventually has to support that.
| const networkInterception = new NetworkInterception(url, filterOptions, browser) | ||
| SESSION_MOCKS[handle].add(networkInterception as Interception) | ||
|
|
||
| networkInterception.remove = () => { |
There was a problem hiding this comment.
I agree, this is not the right place to define this command in and we should find ways to put it in NetworkInterception.
you need networkInterception in order to remove it from set
If you define this method in the NetworkInterception class it is accessible through this.
you need SESSION_MOCKS, to know, where to delete from
SESSION_MOCKS then needs to be defined somewhere where mock.ts and the file where NetworkInterception is defined can import it from without having circular dependencies.
you need handle, to know where to exactly delete from
make remove async and get it via const handle = await browser.getWindowHandle()
you need this client: CDPSession to disable fetch domain,
You have access to the browser variable so you can get that variable too.
| const networkInterception = new NetworkInterception(url, filterOptions, browser) | ||
| SESSION_MOCKS[handle].add(networkInterception as Interception) | ||
|
|
||
| networkInterception.remove = () => { |
There was a problem hiding this comment.
The method mockRestore is meant to do what you want to do with remove .. can we just make sure the restore method ensures that nor memory is hogged?
KuznetsovRoman
left a comment
There was a problem hiding this comment.
Now mock.restore() acts like reversed browser.mock().
| import type { CDPSession } from 'puppeteer-core/lib/esm/puppeteer/common/Connection.js' | ||
|
|
||
| export const SESSION_MOCKS: Record<string, Set<Interception>> = {} | ||
| export const CDP_SESSIONS: Record<string, CDPSession> = {} |
There was a problem hiding this comment.
const client = await page.target().createCDPSession() creates new CDPSession, but we need the original one to disable fetch domain. Storing map<handle, CDPSession> allows us to restore mocks in any order
| * removes any mocked return values or implementations. | ||
| */ | ||
| restore () { | ||
| async restore (sessionMocks = SESSION_MOCKS, cdpSessions = CDP_SESSIONS) { |
There was a problem hiding this comment.
this is for unit tests
| private ensureNotRestored() { | ||
| if (this.restored) { | ||
| throw new Error('This can\'t be done on restored mock') | ||
| } | ||
| } |
There was a problem hiding this comment.
If somebody has tests, where abort / respond is called after restore, currently it would work just like creating the new one with mocked response.
After this commit, abort / respond after restore just won't do anything, so i added this check to let people know, that you can't do that now.
Also, should i include BREAKING CHANGE in commit message footer? That could be considered as breaking change.
|
Also added a commit to handle error responses. Currently, event is considered a request event, if it has no status code, but some non-request events also dont have it. Event example: {
requestId: 'interception-job-3.0',
request: {
url: 'https://foo-bar-baz-qux.com/image.jpeg', // when host is not resolved
method: 'GET',
headers: { ... },
initialPriority: 'Low',
referrerPolicy: 'strict-origin-when-cross-origin'
},
frameId: '1A8B140A1A0109D1F6C38483096CB34F',
resourceType: 'Image',
responseErrorReason: 'NameNotResolved',
networkId: '7803.5'
}I fixed it, so those events would be handled as not-requests, and we would see them. For example: mock.on("match", match => {
console.log(match.statusCode); // will prints "0", if there is no such host. Currently, we just wont have that event.
})0 is also a falsy value, so we could use |
christian-bromann
left a comment
There was a problem hiding this comment.
Could it be that what you are trying to do is more close towards what a restoreAllMocks would do?
| } | ||
|
|
||
| log.trace(`Disabling fetch domain for ${handle}`) | ||
| return cdpSessions[handle].send('Fetch.disable') |
There was a problem hiding this comment.
This would restore mocking for all mocks right? Can you verify that you can create two mocks within the same session and have only one restored?
There was a problem hiding this comment.
this would restore mocking for all mocks right?
we have SESSION_MOCKS, which is map<handle, mock[]>
All mocks of one handle use the same CDP SESSION, and share the same fetch domain
In this implementation i added CDP_SESSIONS, which stores handle -> CDPSession
When we remove last mock of some handle, we dont need to intercept requests for this CDPSession, and it actually disables fetch domain for this handle only.
If we will create two mocks within the same session and then restore just one of them, condition on L#238 will be true, and we will return from this method on L#239б so other mocks of this handle would still act as unrestored. I also included tests for this case:
There was a problem hiding this comment.
Makes sense, so only if all mocks of a specific window are restored, the Fetch.disable command is being called. We should update mockRestoreAll.ts to add an await to the restore call.
There was a problem hiding this comment.
Yup. Fixed in ee4f276
Also changed type here
and used await instead of return here, because Promise<Promise<void>> and Promise<void> are not the same thing
|
@KuznetsovRoman it seems like some unit tests are failing 🤔 |
Yes, it's the one, checking webdriver's mock "restore" method return value. I changed "return" to "await" there, so it now returns undefined, instead of "{}". Do we need this return value, or why do we even need this check in the first place? |
We don't need the return value, just have the expected value be |
|
Should be fixed |
Proposed changes
So
Mockhasclearandrestore, but it does not stop requests/responses from being processed just like active mocks.request/match/continue/ ... events will still be emitted, and now there is no way to prevent it.Example:
Example:
produces this log (tail), where the number comes from
console.log(mock.calls.length):so, it looks like those calls are being saved, and if each test has a mock, then 100-th test will be stored 100 times.
Example:
uses 3.5GB ram:

mock.restoreat the end of test, 100-th test takes 1171 ms, and withmock.remove- 810ms (on simple test:mock = browser.mock('**'); browser.url('localhost:3000'); mock.remove, where localhost:3000 serves react app template, generated with create-react-app).mock.removealso disables fetch domain, if there are no other mocks. I assume, fetch domain has some effect on performance.Types of changes
Checklist
Reviewers: @webdriverio/project-committers