Search code examples
pythonplaywrightplaywright-python

Converting request interception example from Cypress to Playwright Python


The Cypress project has a sample test on how to intercept and modify AJAX calls and I wanted to rewrite the same example in Playwright for Python (pytest).

The example consists on intercepting and modifying an Ajax call that feeds the Comments section of a webpage.

This is the Cypress code. The test successfully passes.

it('cy.intercept() - route responses to matching requests', () => {

    cy.visit('https://example.cypress.io/commands/network-requests')
    let message = 'whoa, this comment does not exist'

    // Stub a response to PUT comments/ ****
    cy.intercept({
      method: 'PUT',
      url: '**/comments/*',
    }, {
      statusCode: 404,
      body: { error: message },
      headers: { 'access-control-allow-origin': '*' },
      delayMs: 500,
    }).as('putComment')

    // we have code that puts a comment when the button is clicked in scripts.js
    cy.get('.network-put').click()

    cy.wait('@putComment')

    // our 404 statusCode logic in scripts.js executed
    cy.get('.network-put-comment').should('contain', message)
})

This is the Playwright for Python (pytest) version that I came up with:

def test_network_intercept(page: Page):
    page.goto("https://example.cypress.io/commands/network-requests")
    message = "whoa, this comment does not exist"

    def handle_route(route: Route):
        route.fulfill(
            status=404,
            body="{'error': '" + message + "'}",
            headers={"access-control-allow-origin": "*"},
        )
    page.route("**/comments/*", handle_route)
    page.locator(".network-put").click()
    expect(page.locator(".network-put-comment")).to_contain_text(message)

This test fails in the last line

expect(page.locator(".network-put-comment")).to_contain_text(message)

Can anybody tell me what is wrong with my code and how to fix it ?


Solution

  • You're very close, but body="{'error': '" + message + "'}", looks iffy. JSON doesn't allow single-quoted keys and values like that. Your response also doesn't specify the content-type header as application/json.

    The following passes, using json= rather than body=, letting Playwright encode the dictionary into JSON and set the appropriate headers:

    from playwright.sync_api import expect, Page, Route, sync_playwright  # 1.37.0
    
    
    def test_network_intercept(page: Page):
        page.goto("https://example.cypress.io/commands/network-requests")
        message = "whoa, this comment does not exist"
    
        def handle_route(route: Route):
            route.fulfill(
                status=404,
                json={"error": message},
                headers={"access-control-allow-origin": "*"},
            )
    
        page.route("**/comments/*", handle_route)
        page.locator(".network-put").click()
        expect(page.locator(".network-put-comment")).to_contain_text(message)
    
    
    if __name__ == "__main__":
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=True)
            page = browser.new_page()
            test_network_intercept(page)
    

    For reference, if you wanted to do what json= does by hand, it'd look like:

    import json
    
    # ...
    body=json.dumps({"error": message}),  # or hand-encode it, but with double quotes
    headers={"content-type": "application/json"},  # add appropriate response type
    # ...
    

    To help debug your original code, you can run headfully or add logging before goto to see if there are errors in the console:

    page.on("pageerror", lambda exc: print(f"uncaught exception: {exc}"))
    page.goto() # ...
    

    This gives uncaught exception: Cannot read properties of undefined (reading 'error'). Not the clearest error, but indicates that the response body isn't coming through correctly.