From 0adf5536afc7084b229bca5887c432c0bb12ef3c Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Mon, 21 Mar 2022 13:20:17 -0700 Subject: [PATCH] docs(fetch): clarify cookie handling (#12892) --- docs/src/api/class-apirequest.md | 8 +-- docs/src/api/class-apirequestcontext.md | 20 ++++++- docs/src/api/class-page.md | 3 +- docs/src/test-api-testing-js.md | 70 +++++++++++++++++++++++ packages/playwright-core/types/types.d.ts | 37 +++++++++--- 5 files changed, 123 insertions(+), 15 deletions(-) diff --git a/docs/src/api/class-apirequest.md b/docs/src/api/class-apirequest.md index 199c2396d5..c48588c59d 100644 --- a/docs/src/api/class-apirequest.md +++ b/docs/src/api/class-apirequest.md @@ -1,10 +1,10 @@ # class: APIRequest * langs: js, java, python -Exposes API that can be used for the Web API testing. Each Playwright browser context -has a APIRequestContext instance attached which shares cookies with the page context. -Its also possible to create a new APIRequestContext instance manually. For more information -see [here](./class-apirequestcontext). +Exposes API that can be used for the Web API testing. This class is used for creating +[APIRequestContext] instance which in turn can be used for sending web requests. An instance +of this class can be obtained via [`property: Playwright.request`]. For more information +see [APIRequestContext]. ## async method: APIRequest.newContext * langs: js, java, python diff --git a/docs/src/api/class-apirequestcontext.md b/docs/src/api/class-apirequestcontext.md index 588d3c7205..de05aedf22 100644 --- a/docs/src/api/class-apirequestcontext.md +++ b/docs/src/api/class-apirequestcontext.md @@ -2,9 +2,23 @@ * langs: js, java, python This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare -environment or the service to your e2e test. When used on [Page] or a [BrowserContext], this API will automatically use -the cookies from the corresponding [BrowserContext]. This means that if you log in using this API, your e2e test -will be logged in and vice versa. +environment or the service to your e2e test. + +Each Playwright browser context has associated with it [APIRequestContext] instance which shares cookie storage with +the browser context and can be accessed via [`property: BrowserContext.request`] or [`property: Page.request`]. +It is also possible to create a new APIRequestContext instance manually by calling [`method: APIRequest.newContext`]. + +**Cookie management** + +[APIRequestContext] retuned by [`property: BrowserContext.request`] and [`property: Page.request`] shares cookie +storage with the corresponding [BrowserContext]. Each API request will have `Cookie` header populated with the +values from the browser context. If the API response contains `Set-Cookie` header it will automatically update +[BrowserContext] cookies and requests made from the page will pick them up. This means that if you log in using +this API, your e2e test will be logged in and vice versa. + +If you want API requests to not interfere with the browser cookies you shoud create a new [APIRequestContext] by +calling [`method: APIRequest.newContext`]. Such `APIRequestContext` object will have its own isolated cookie +storage. ```python async import os diff --git a/docs/src/api/class-page.md b/docs/src/api/class-page.md index 6821f13397..1e75a80125 100644 --- a/docs/src/api/class-page.md +++ b/docs/src/api/class-page.md @@ -2558,7 +2558,8 @@ last redirect. * langs: js, java, python - type: <[APIRequestContext]> -API testing helper associated with this page. Requests made with this API will use page cookies. +API testing helper associated with this page. This method returns the same instance as +[`property: BrowserContext.request`] on the page's context. See [`property: BrowserContext.request`] for more details. ## async method: Page.route diff --git a/docs/src/test-api-testing-js.md b/docs/src/test-api-testing-js.md index 1960bdc7fe..c272b9eedf 100644 --- a/docs/src/test-api-testing-js.md +++ b/docs/src/test-api-testing-js.md @@ -387,3 +387,73 @@ await requestContext.storageState({ path: 'state.json' }); // Create a new context with the saved storage state. const context = await browser.newContext({ storageState: 'state.json' }); ``` + +## Context request vs global request + +There are two types of [APIRequestContext]: +* associated with a [BrowserContext] +* isolated instance, created via [`method: APIRequest.newContext`] + +The main difference is that [APIRequestConxtext] accessible via [`property: BrowserContext.request`] and +[`property: Page.request`] will populate request's `Cookie` header from the browser context and will +automatically update browser cookies if [APIResponse] has `Set-Cookie` header: + +```js +test('context request will share cookie storage with its browser context', async ({ page, context }) => { + await context.route('https://www.github.com/', async (route) => { + // Send an API request that shares cookie storage with the browser context. + const response = await context.request.fetch(route.request()); + const responseHeaders = response.headers(); + + // The response will have 'Set-Cookie' header. + const responseCookies = new Map(responseHeaders['set-cookie'].split('\n').map(c => c.split(';', 2)[0].split('='))); + // The response will have 3 cookies in 'Set-Cookie' header. + expect(responseCookies.size).toBe(3); + const contextCookies = await context.cookies(); + // The browser context will already contain all the cookies from the API response. + expect(new Map(contextCookies.map(({name, value}) => [name, value]))).toEqual(responseCookies); + + route.fulfill({ + response, + headers: {...responseHeaders, foo: 'bar'}, + }); + }); + await page.goto('https://www.github.com/'); +}); +``` + +If you don't want [APIRequestContext] to use and update cookies from the browser context, you can manually +create a new instance of [APIRequestContext] which will have its own isolated cookies: + +```js +test('global context request has isolated cookie storage', async ({ page, context, browser, playwright }) => { + // Create a new instance of APIRequestContext with isolated cookie storage. + const request = await playwright.request.newContext(); + await context.route('https://www.github.com/', async (route) => { + const response = await request.fetch(route.request()); + const responseHeaders = response.headers(); + + const responseCookies = new Map(responseHeaders['set-cookie'].split('\n').map(c => c.split(';', 2)[0].split('='))); + // The response will have 3 cookies in 'Set-Cookie' header. + expect(responseCookies.size).toBe(3); + const contextCookies = await context.cookies(); + // The browser context will not have any cookies from the isolated API request. + expect(contextCookies.length).toBe(0); + + // Manually export cookie storage. + const storageState = await request.storageState(); + // Create a new context and initialize it with the cookies from the global request. + const browserContext2 = await browser.newContext({ storageState }); + const contextCookies2 = await browserContext2.cookies(); + // The new browser context will already contain all the cookies from the API response. + expect(new Map(contextCookies2.map(({name, value}) => [name, value]))).toEqual(responseCookies); + + route.fulfill({ + response, + headers: {...responseHeaders, foo: 'bar'}, + }); + }); + await page.goto('https://www.github.com/'); + await request.dispose(); +}); +``` \ No newline at end of file diff --git a/packages/playwright-core/types/types.d.ts b/packages/playwright-core/types/types.d.ts index 28bba91bdc..4fddaf3930 100644 --- a/packages/playwright-core/types/types.d.ts +++ b/packages/playwright-core/types/types.d.ts @@ -2891,7 +2891,10 @@ export interface Page { }): Promise; /** - * API testing helper associated with this page. Requests made with this API will use page cookies. + * API testing helper associated with this page. This method returns the same instance as + * [browserContext.request](https://playwright.dev/docs/api/class-browsercontext#browser-context-request) on the page's + * context. See [browserContext.request](https://playwright.dev/docs/api/class-browsercontext#browser-context-request) for + * more details. */ request: APIRequestContext; @@ -11910,9 +11913,10 @@ export interface AndroidWebView { } /** - * Exposes API that can be used for the Web API testing. Each Playwright browser context has a APIRequestContext instance - * attached which shares cookies with the page context. Its also possible to create a new APIRequestContext instance - * manually. For more information see [here](https://playwright.dev/docs/class-apirequestcontext). + * Exposes API that can be used for the Web API testing. This class is used for creating [APIRequestContext] instance which + * in turn can be used for sending web requests. An instance of this class can be obtained via + * [playwright.request](https://playwright.dev/docs/api/class-playwright#playwright-request). For more information see + * [APIRequestContext]. */ export interface APIRequest { /** @@ -12037,9 +12041,28 @@ export interface APIRequest { /** * This API is used for the Web API testing. You can use it to trigger API endpoints, configure micro-services, prepare - * environment or the service to your e2e test. When used on [Page] or a [BrowserContext], this API will automatically use - * the cookies from the corresponding [BrowserContext]. This means that if you log in using this API, your e2e test will be - * logged in and vice versa. + * environment or the service to your e2e test. + * + * Each Playwright browser context has associated with it [APIRequestContext] instance which shares cookie storage with the + * browser context and can be accessed via + * [browserContext.request](https://playwright.dev/docs/api/class-browsercontext#browser-context-request) or + * [page.request](https://playwright.dev/docs/api/class-page#page-request). It is also possible to create a new + * APIRequestContext instance manually by calling + * [apiRequest.newContext([options])](https://playwright.dev/docs/api/class-apirequest#api-request-new-context). + * + * **Cookie management** + * + * [APIRequestContext] retuned by + * [browserContext.request](https://playwright.dev/docs/api/class-browsercontext#browser-context-request) and + * [page.request](https://playwright.dev/docs/api/class-page#page-request) shares cookie storage with the corresponding + * [BrowserContext]. Each API request will have `Cookie` header populated with the values from the browser context. If the + * API response contains `Set-Cookie` header it will automatically update [BrowserContext] cookies and requests made from + * the page will pick them up. This means that if you log in using this API, your e2e test will be logged in and vice + * versa. + * + * If you want API requests to not interfere with the browser cookies you shoud create a new [APIRequestContext] by calling + * [apiRequest.newContext([options])](https://playwright.dev/docs/api/class-apirequest#api-request-new-context). Such + * `APIRequestContext` object will have its own isolated cookie storage. * */ export interface APIRequestContext {