Skip to content
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

Intercept Network calls #98

Closed
saikrishna321 opened this issue Aug 7, 2018 · 22 comments
Closed

Intercept Network calls #98

saikrishna321 opened this issue Aug 7, 2018 · 22 comments
Assignees

Comments

@saikrishna321
Copy link
Member

@zabil - In puppeteer, we can intercept API calls, continue or abort network request/response.

Can this be implemented in taiko ?

Code in puppeteer

async function automateBrowser({port}) {
	const browser = await puppeteer.launch({
		headless: false
	});

	const page = await browser.newPage();
	await page.setRequestInterceptionEnabled(true);

	page.on('request', request => {
		const URLToIntercept = 'https://forms.hubspot.com/embed/v3/form/2059467/2e1a1b5b-27bb-447d-aac4-0b87c1e88fec?callback=hs_reqwest_0&hutk=';

		if (request.url === URLToIntercept) {
			request.continue({
				url: `http://127.0.0.1:${port}/form.txt`
			});
		} else {
			request.continue();
		}
	});

	await page.goto('https://www.sitepen.com/site/contact.html');

	await browser.close();
	process.exit();
}
@zabil
Copy link
Member

zabil commented Aug 8, 2018

It's possible right now.
Taiko exposes the CRI client with the Network object.

However it'll require some digging into CRI's docs.

To understand this better, intercepting requests to provide test data? Maybe add an easier API something like

intercept("https://example.com", "http://proxy.com")

and for finer control

intercept("https://example.com", request => {})

@saikrishna321
Copy link
Member Author

intercepting requests to provide test data?

Yeah, we can mock any api call with test data.

https://github.com/cyrus-and/chrome-remote-interface/wiki/Intercept-and-block-requests-by-URL

@NivedhaSenthil
Copy link
Member

WIP

Example1:

intercept({ 
        request: {url:'https://taiko.gauge.org/assets/css/pilcrow.css'},
        mock: {errorReason:"Failed"}
})

Example2:

intercept({ 
        request: {url:'https://taiko.gauge.org/assets/css/pilcrow.css'},
        mock: {response:{status: 404, contentType: 'text/plain',body: 'Not Found!'}}
})

Example3:

intercept({ 
        request: {url:'https://taiko.gauge.org/assets/css/pilcrow.css'},
        callback: doOnIntercept
})

intercept api will take an object with request to intercept and action to be done on intercepted request.

  • request can be matched using any one from (url, post-data, method, resourceType, headers)
  • mock can be used to do various things like below
    • override request details like url, method, post-data, headers
      eg: mock:{ url: 'http://proxy', method: 'GET' }
    • modify response details like status, header, content-type, body
      eg: mock: {response:{status: 404, contentType: 'text/plain',body: 'Not Found!'}}
    • abort an request
      eg: mock: {errorReason:"Failed"}
  • callback can be used to do any action after interception

@nehashri
Copy link
Contributor

A small change here would be useful,
The callback should run when the request is intercepted. It should take the request as its parameters and return a mock response object.
Example

intercept({ 
        request: {url:'https://taiko.gauge.org/assets/css/pilcrow.css'},
        callback: function doOnIntercept(request) {
                      // do something
                      return mockResponse;
                 }
})

This gives the user ability to intercept the request, process any data it may contain and return a mock response dynamically.

@zabil
Copy link
Member

zabil commented Aug 22, 2018

For first pass we must make this very simple

If we approach this from a testing perspective

Case 1: Mock the requested url with a test data url

intercept("foo", "bar")
  • foo can be a url pattern or regex.
  • The test data url bar must get all the headers and data of foo

Case 2: Return JSON

intercept("foo", {bar: "baz"})

Case 3: Return the result of a function

intercept("foo", (request) => return "baz");
  • the request object must have details of the data, original url, headers

@saikrishna321
Copy link
Member Author

@zabil i like the First approach 👍

Can i know which approach has the team decided to go with ?

@NivedhaSenthil
Copy link
Member

@zabil if we match only using url user may not have flexibility to identify any request with specific method, header, or post-data.

can it be like intercept(requestObject, handler) where handler can be a mock object or callback which will return a mock object.

eg: intercept({url:'http://something.com',method:'get'}, (request) => return 'baz');

@zabil
Copy link
Member

zabil commented Sep 6, 2018

Because the application implements the request method, setting it is redundant so {url:'http://something.com',method:'get'} just be http://something.com and there probably won't be a case where GET has to change to POST and vice versa.

For headers they can be read/modified from

(request) => return 'baz'

@NivedhaSenthil
Copy link
Member

We will be following below design for intercepting APIs

case 1: block url => intercept(url)
case 2: mockResponse => intercept(url,{mockObject})
case 3: override request => intercept(url,(request) => {request.continue({overrideObject})})
case 4: redirect => intercept(url,redirectUrl)
case 5: mockResponse based on request =>
intercept(url,(request) => { //someaction; request.respond({mockResponseObject})} )

@NivedhaSenthil
Copy link
Member

Regex to match URLs will be handle in #142

@NivedhaSenthil
Copy link
Member

Fix should be available in 7e4fdc3.

Below is an example script for all 5 cases mentioned in the issue. Steps to run the sample script,

#code.js

const assert = require("assert");
(async () => {
    try {
        await openBrowser({headless:false});
        
        //Case 1: Block URL (Blocks all request for people.png)
        await intercept("http://localhost:3000/assets/images/people.png");

        //Case 2: Mock response (Mocks response to modify address from "888 Central St." to "12345 Central St." )
        await intercept("http://localhost:3000/api/customers/11",{body: '{"id":11,"firstName":"ward","lastName":"bell","gender":"male","address":"12345 Central St.","city":"Los Angeles","state":{"abbreviation":"CA","name":"California"},"latitude":34.042552,"longitude":-118.266429}'});

        //Case 3: Override request (Overrides google4_hdpi.png url to return female.png can be seen in map)
        await intercept("https://maps.gstatic.com/mapfiles/api-3/images/google4_hdpi.png",(request) => {request.continue({url:"http://localhost:3000/assets/images/female.png"})})
        
        //Case 4: Redirect URL (Overrides male.png to female.png)
        await intercept("http://localhost:3000/assets/images/male.png","http://localhost:3000/assets/images/female.png")
        
        //Case 5: Mock response based on request (Mocks response to modify address from "1234 Anywhere St." to "888 Anywhere St.")
        await intercept("http://localhost:3000/api/customers/1", (request) => {request.respond({body:'{"id":1,"firstName":"ted","lastName":"james","gender":"male","address":"888 Anywhere St.","city":" Phoenix ","state":{"abbreviation":"AZ","name":"Arizona"},"orders":[{"productName":"Basketball","itemCost":7.99},{"productName":"Shoes","itemCost":199.99}],"latitude":33.299,"longitude":-111.963}'})});
        
        await goto("localhost:3000");
        await click(link("2"));
        await click(link("Ward Bell"));
        assert.equal("Exists",(await contains("12345 Central St.").exists()).description);
        await click(link("Customers"));
        await click(link("Ted James"));
        assert.equal("Exists",(await contains("888 Anywhere St.").exists()).description);
    
    } catch (e) {
        console.error(e);
    } finally {
        await closeBrowser();
    }
})();

@sswaroopgupta
Copy link
Contributor

Getting Error: ENOENT: no such file or directory, stat '.../Angular-JumpStart/dist/index.html' on npm install && npm start

@NivedhaSenthil
Copy link
Member

Have missed ng build step in between, try doing it before npm start

@vitor-gyant
Copy link

Hi @NivedhaSenthil ,

If I want to add a new key to the postData is this the best way to do it ?

await intercept(`${baseUri}/api/create-account`, (request) => {
      request.continue({ postData: `${request.request.postData}&isTestingUser=true` });
});

Is there another way to do this ?

Thanks,

@zabil
Copy link
Member

zabil commented Feb 6, 2020

@vitor-gyant have you tried

request.request.postData.isTestingUser = true
request.continue(request.request.postData);

NivedhaSenthil added a commit that referenced this issue Aug 31, 2020
* Added type test from specs for openBrowser, closeBrowser

The commit adds some tests for the functions openBrowser and closeBrowser.
The tests are based on the examples provided in the official documentation.

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

* Added type test for switchTo and fixed switchTo type def

This commit adds tests for switchTo type definition. The tests are
derived from the examples in the official documentation.

Adding those tests shows that the type definition was not correct.
This commit fixes the types of the arguments of switchTo function.

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

* Added type test for intercept and fixed type def

This commit adds tests for intercept type definition. The tests are
derived from the examples in the official documentation and from
Nivedha's comment here
#98 (comment).

Adding those tests shows that the type definition was not correct.
This commit fixes the types of the arguments of intercept function.

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

* Added type tests for emulateNetwork, emulateDevice, setViewport

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

* Added type test for openTab and fixed type def

This commit adds tests for openTab type definition. The tests are
derived from the examples in the official documentation.

Adding those tests shows that the type definition was not correct.
This commit fixes the types of the arguments of openTab function.

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

* Added type test for closeTab and fixed type def

This commit adds tests for closeTab type definition. The tests are
derived from the examples in the official documentation.

Adding those tests shows that the type definition was not correct.
This commit fixes the types of the arguments of openTab function.

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

* Added type test for several taiko functions

This commit adds tests for type definitions for:

- openIncognitoWindow
- closeIncognitoWindow
- overridePermissions,
- clearPermissionOverrides

The tests are derived from the examples in the official documentation.

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

* Added type tests for the remaining browser actions

This commit adds tests for type definitions for:

- setCookie
- deleteCookies
- getCookies
- setLocation
- clearIntercept

The tests are derived from the examples in the official documentation.

This commit also introduces the concept of getting some types from
the official devtools-protocol repository.

Signed-off-by: Filippo Maria Panelli <filippo.panelli@gmail.com>

Co-authored-by: Nivedha <nivedhasenthil@gmail.com>
@rmallof
Copy link

rmallof commented Sep 15, 2020

Hi @zabil @NivedhaSenthil

I'm testing the intercept functionality with the form at https://tryphp.w3schools.com/showphp.php?filename=demo_form_post while intercepting https://tryphp.w3schools.com/demo/welcome.php using:

step(["Step"], async () => {
    await intercept("https://tryphp.w3schools.com/demo/welcome.php", (request) => { 
        console.log("@ Intercept step callback");
        let parameters = querystring.parse(request.request.postData)
        parameters["name"] = "This comes from Taiko";    <--------------- overwritting "name" variable with "This comes from Taiko"
        request.request.postData = querystring.stringify(parameters);
        request.continue({ postData: request.request.postData });
    });
});

I'm able to modify the parameters in request.request.postData but request.continue() is not taking effect - the data shown at https://tryphp.w3schools.com/showphp.php?filename=demo_form_post after sending the form is the one written in the textBox instead of "This comes from Taiko"

Am I missing something?

Thank you

@NivedhaSenthil
Copy link
Member

yes, I could see it is broken. Taking a look at it, thanks for reporting

@rmallof
Copy link

rmallof commented Sep 16, 2020

Thanks @NivedhaSenthil. Please, let me know if I should open a new issue for this behavior

@zabil
Copy link
Member

zabil commented Sep 16, 2020

@rmallof yes, that will be good.

@rmallof
Copy link

rmallof commented Sep 16, 2020

Done: #1520

Thanks @zabil @NivedhaSenthil !

@ramirobg94
Copy link

Does it work with headless:true?

@NivedhaSenthil
Copy link
Member

Yes it should work in both headless and headful mode

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

8 participants