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

Retrieve all cookies #1005

Merged
merged 15 commits into from
Aug 25, 2023
36 changes: 35 additions & 1 deletion api/browser_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type BrowserContext interface {
ClearCookies() error
ClearPermissions()
Close()
Cookies() ([]any, error) // TODO: make it []Cookie later on
Cookies() ([]*Cookie, error)
ExposeBinding(name string, callback goja.Callable, opts goja.Value)
ExposeFunction(name string, callback goja.Callable)
GrantPermissions(permissions []string, opts goja.Value)
Expand All @@ -36,3 +36,37 @@ type BrowserContext interface {
Unroute(url goja.Value, handler goja.Callable)
WaitForEvent(event string, optsOrPredicate goja.Value) any
}

// Cookie represents a browser cookie.
//
// https://datatracker.ietf.org/doc/html/rfc6265.
type Cookie struct {
Name string `json:"name"` // Cookie name.
Value string `json:"value"` // Cookie value.
Domain string `json:"domain"` // Cookie domain.
Path string `json:"path"` // Cookie path.
Expires int64 `json:"expires"` // Cookie expiration date as the number of seconds since the UNIX epoch.
HTTPOnly bool `json:"httpOnly"` // True if cookie is http-only.
Secure bool `json:"secure"` // True if cookie is secure.
SameSite CookieSameSite `json:"sameSite"` // Cookie SameSite type.
}

// CookieSameSite represents the cookie's 'SameSite' status.
//
// https://tools.ietf.org/html/draft-west-first-party-cookies.
type CookieSameSite string

const (
// CookieSameSiteStrict sets the cookie to be sent only in a first-party
// context and not be sent along with requests initiated by third party
// websites.
CookieSameSiteStrict CookieSameSite = "Strict"

// CookieSameSiteLax sets the cookie to be sent along with "same-site"
// requests, and with "cross-site" top-level navigations.
CookieSameSiteLax CookieSameSite = "Lax"

// CookieSameSiteNone sets the cookie to be sent in all contexts, i.e
// potentially insecure third-party requests.
CookieSameSiteNone CookieSameSite = "None"
)
19 changes: 7 additions & 12 deletions browser/mapping.go
Original file line number Diff line number Diff line change
Expand Up @@ -613,18 +613,13 @@ func mapWorker(vu moduleVU, w api.Worker) mapping {
func mapBrowserContext(vu moduleVU, bc api.BrowserContext) mapping {
rt := vu.Runtime()
return mapping{
"addCookies": bc.AddCookies,
"addInitScript": bc.AddInitScript,
"browser": bc.Browser,
"clearCookies": bc.ClearCookies,
"clearPermissions": bc.ClearPermissions,
"close": bc.Close,
"cookies": func() ([]any, error) {
cc, err := bc.Cookies()
ctx := vu.Context()
panicIfFatalError(ctx, err)
return cc, err //nolint:wrapcheck
},
"addCookies": bc.AddCookies,
"addInitScript": bc.AddInitScript,
"browser": bc.Browser,
"clearCookies": bc.ClearCookies,
"clearPermissions": bc.ClearPermissions,
"close": bc.Close,
"cookies": bc.Cookies,
"exposeBinding": bc.ExposeBinding,
"exposeFunction": bc.ExposeFunction,
"grantPermissions": bc.GrantPermissions,
Expand Down
45 changes: 42 additions & 3 deletions common/browser_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,46 @@ func (b *BrowserContext) ClearCookies() error {
return nil
}

// Cookies is not implemented.
func (b *BrowserContext) Cookies() ([]any, error) {
return nil, fmt.Errorf("BrowserContext.cookies() has not been implemented yet: %w", k6error.ErrFatal)
// Cookies returns all cookies.
// Some of them can be added with the AddCookies method and some of them are
// automatically taken from the browser context when it is created. And some of
// them are set by the page, i.e., using the Set-Cookie HTTP header or via
// JavaScript like document.cookie.
func (b *BrowserContext) Cookies() ([]*api.Cookie, error) {
b.logger.Debugf("BrowserContext:Cookies", "bctxid:%v", b.id)

// get cookies from this browser context.
getCookies := storage.
GetCookies().
WithBrowserContextID(b.id)
networkCookies, err := getCookies.Do(
cdp.WithExecutor(b.ctx, b.browser.conn),
)
if err != nil {
return nil, fmt.Errorf("getting cookies from the browser context: %w", err)
}

// return if no cookies found so we don't have to needlessly convert them.
// users can still work with cookies using the empty slice.
// like this: cookies.length === 0.
if len(networkCookies) == 0 {
return nil, nil
}

// convert the received CDP cookies to the browser API format.
cookies := make([]*api.Cookie, len(networkCookies))
for i, c := range networkCookies {
cookies[i] = &api.Cookie{
Name: c.Name,
Value: c.Value,
Domain: c.Domain,
Path: c.Path,
Expires: int64(c.Expires),
HTTPOnly: c.HTTPOnly,
Secure: c.Secure,
SameSite: api.CookieSameSite(c.SameSite),
}
}

return cookies, nil
}
50 changes: 50 additions & 0 deletions examples/cookies.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { check } from 'k6';
import { browser } from 'k6/x/browser';

export const options = {
scenarios: {
ui: {
executor: 'shared-iterations',
options: {
browser: {
type: 'chromium',
},
},
},
},
thresholds: {
checks: ["rate==1.0"]
}
};

export default async function () {
const page = browser.newPage();
const context = page.context();

try {
// get cookies from the browser context
check(context.cookies().length, {
'initial number of cookies should be zero': n => n === 0,
});

// add some cookies to the browser context
ka3de marked this conversation as resolved.
Show resolved Hide resolved
context.addCookies([{name: 'testcookie', value: '1', sameSite: 'Strict', domain: '127.0.0.1', path: '/'}]);
context.addCookies([{name: 'testcookie2', value: '2', sameSite: 'Lax', domain: '127.0.0.1', path: '/'}]);

check(context.cookies().length, {
'number of cookies should be 2': n => n === 2,
});

const cookies = context.cookies();
check(cookies[0], {
'cookie 1 name should be testcookie': c => c.name === 'testcookie',
'cookie 1 value should be 1': c => c.value === '1',
});
check(cookies[1], {
'cookie 2 name should be testcookie2': c => c.name === 'testcookie2',
'cookie 2 value should be 2': c => c.value === '2',
});
} finally {
page.close();
}
}
8 changes: 8 additions & 0 deletions examples/fillform.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ export default async function() {
check(page, {
'header': page.locator('h2').textContent() == 'Welcome, admin!',
});

// Check whether we receive cookies from the logged site.
check(context.cookies(), {
'session cookie is set': cookies => {
const sessionID = cookies.find(c => c.name == 'sid')
return typeof sessionID !== 'undefined'
}
})
} finally {
page.close();
}
Expand Down
Loading
Loading