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

[Bug]: Browser object is not restarted after a passed test for two consecutive test.describe #30289

Closed
tongilcoto opened this issue Apr 8, 2024 · 6 comments

Comments

@tongilcoto
Copy link

Version

1.43

Steps to reproduce

  1. Clone my https://github.com/tongilcoto/playwright-browser-does-not-restart
  2. npm install
  3. options:

Expected Behaviour: After the test of the describe is finished, the browser object gets restarted by the framework

npx playwright test Expected-Behaviour-1st-test-failed.spec.ts --headed

You will see:

  1. The browser object (chromium) is opened for the first test. The test fails.
  2. The browser object is closed
  3. The browser object is opened for running the second describe

Issue #1: The browser object is not restarted

npx playwright test Issue-1-1st-test-passed.spec.ts --headed

You will see:

  1. The browser object (chromium) is opened for the first test. The test passed.
  2. The browser object is not closed
  3. A new window is opened for running the second describe (two opened windows)

Issue #2: The browser object is forced to close via browser.close(), but the second describe cannot reopened it

npx playwright test Issue-2-1st-test-passed-and-afterEach-Close.spec.ts --headed

You will see:

  1. The browser object (chromium) is opened for the first test. The test passes.
  2. The browser object is closed at the afterEach hook
  3. The second describe throws an error because cannot open the browser object
    Error: browser.newContext: Target page, context or browser has been closed

      18 | test.describe('Example - Describe 2', () => {
      19 |   test('get started link', async ({ browser }) => {
    > 20 |     const context = await browser.newContext()
         |                     ^
      21 |     const page = await context.newPage()
      22 |     await page.goto('https://playwright.dev/');

Bonus: While keeping the use of browser.close at the AfterEach, If there is a third test.describe, while the second test.describe fails, the third one doesn't, it is able to restart the browser object

npx playwright test Bonus-with-3-test-describe.spec.ts --headed

You will see:

  1. The browser object (chromium) is opened for the first test. The test passes.
  2. The browser object is closed at the afterEach hook
  3. The second describe throws an error because cannot open the browser object
  4. The third describe restarts the browser object and it passes

Expected behavior

As explained in "Steps to reproduce" the expected behaviour is to close at least the page, in order to not use unneeded memory.
Besides screenshot feature, if not well managed, could save two screenshots while only one needed.

Actual behavior

As explained in "Steps to reproduce" the actual behaviour is that you end up with 2 opened windows

Additional context

No response

Environment

System:
    OS: macOS 14.4
    CPU: (10) arm64 Apple M2 Pro
    Memory: 341.28 MB / 16.00 GB
  Binaries:
    Node: 18.15.0 - ~/.nvm/versions/node/v18.15.0/bin/node
    npm: 9.5.0 - ~/.nvm/versions/node/v18.15.0/bin/npm
  Languages:
    Bash: 3.2.57 - /bin/bash
  npmPackages:
    @playwright/test: ^1.43.0 => 1.43.0
@Rokandor
Copy link

Rokandor commented Apr 9, 2024

I am also facing this issue
Error:Target page, context or browser has been closed
And it happens randomly as well without more details

@mxschmitt
Copy link
Member

Playwright has 3 fundamental objects when interacting with the browser.

  1. Browser: Its like if you launch Google Chrome on your machine
  2. Browser Context: Its like if you open an incognito window. You can open multiple, they will be isolated from each-other.
  3. Page: A Browser Context contains one or multiple Pages.

In your case, you forgot to close the Browser Context which you were creating in issue 1, this one was then still alive for the second test. For the issue 2, you were closing the browser, which we re-use for multiple tests, hence you then get an error in the second test, that the browser is already closed. Doing the following would fix it:

diff --git a/tests/Issue-1-1st-test-passed.spec.ts b/tests/Issue-1-1st-test-passed.spec.ts
index 7b3dd9d..5381699 100644
--- a/tests/Issue-1-1st-test-passed.spec.ts
+++ b/tests/Issue-1-1st-test-passed.spec.ts
@@ -8,6 +8,7 @@ test.describe('Example - Describe 1', () => {
   
     // Expect a title "to contain" a substring.
     await expect(page).toHaveTitle(/Playwright/);
+    await context.close();
   })
 })
 
@@ -22,5 +23,7 @@ test.describe('Example - Describe 2', () => {
 
     // Expects page to have a heading with the name of Installation.
     await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
+    await context.close();
   })
 })

Issue 2:

diff --git a/tests/Issue-2-1st-test-passed-and-afterEach-Close.spec.ts b/tests/Issue-2-1st-test-passed-and-afterEach-Close.spec.ts
index fe176a0..052bae1 100644
--- a/tests/Issue-2-1st-test-passed-and-afterEach-Close.spec.ts
+++ b/tests/Issue-2-1st-test-passed-and-afterEach-Close.spec.ts
@@ -8,10 +8,7 @@ test.describe('Example - Describe 1', () => {
   
     // Expect a title "to contain" a substring.
     await expect(page).toHaveTitle(/Playwright/);
-  })
-
-  test.afterEach(async ({ browser }) => {
-    await browser.close()
+    await context.close()
   })
 })
 
@@ -26,5 +23,6 @@ test.describe('Example - Describe 2', () => {
 
     // Expects page to have a heading with the name of Installation.
     await expect(page.getByRole('heading', { name: 'Installation' })).toBeVisible();
+    await context.close();
   })
 })

See here: https://playwright.dev/docs/api/class-browsercontext

@Rokandor
Copy link

Rokandor commented Apr 9, 2024

Hey @mxschmitt , I think I have a different problem, I'm writing a POM project.

here is my test:

// @ts-check
const { chromium } = require('playwright');
const { test, expect } = require('@playwright/test');
const HomePage = require('../pages/HomePage');
const SearchPage = require('../pages/SearchPage.js');
const validUseCases = require('../scripts/generateValidUseCases.js');
const { PlaywrightBlocker } = require('@cliqz/adblocker-playwright');

let browser;
let context;
let page;

test.beforeEach(async () => {
  browser = await chromium.launch();
  context = await browser.newContext();
  page = await context.newPage();
});

/*test.afterEach(async () => {
  await browser.close();
})*/

test('Navigation to Rocket Jump Ninja Home page', async () => {
  const homePage = new HomePage(page);
  await test.step('Block Ads', async () => {
    await homePage.blockGoogleAds(page);
  });
  await test.step('Navigate to home page', async () => {
    await homePage.navigateToRocketJumpNinja('https://www.rocketjumpninja.com/');
  });
});

// test.describe('New Valid Search', () => { 
test('Validate Search form includind default values', async () => {
  const homePage = new HomePage(page);
  const searchPage = new SearchPage(page);

  await test.step('Block Ads', async () => {
    await homePage.blockGoogleAds(page);
  });

  await test.step('Navigate to search page', async () => {
    await homePage.navigateToRocketJumpNinja('https://www.rocketjumpninja.com/');
    await homePage.goToSearch();
    await homePage.blockGoogleAds(page);
  });
  await test.step('Validate default value of the search form', async () => {
    searchPage.validateSearchFormDefaultValues();
  });
});

test('Fill search mouse with shuffled valid user cases', async () => {
  const homePage = new HomePage(page);
  const searchPage = new SearchPage(page);

  await test.step('Block Ads', async () => {
    await homePage.blockGoogleAds(page);
  });

  await test.step('Navigate to search page', async () => {
    await homePage.navigateToRocketJumpNinja('https://www.rocketjumpninja.com/');
    await homePage.goToSearch();
    await homePage.blockGoogleAds(page);
  });
  await test.step('Fill search form using valid shuffle use cases', async () => {
    try {
      await searchPage.fillSearchShuffleValidUseCase(validUseCases);
  }catch (error) {
    console.error('Error occurred:', error.message);
    console.error('Stack trace:', error.Stack);
  }
});
});

SearchPage:

// @ts-check
const { expect } = require('@playwright/test');
const HomePage = require('./HomePage.js');
//const validUseCases = require('../scripts/generateValidUseCases.js');
const { shuffleArray } = require('../functions/array.js');
// const { shuffleArray } = require('../functions/array.js');
const { AssertionError } = require('assert');

class SearchPage extends HomePage {

    constructor(page) {
        super(page);
        this.measurementDD = page.locator("#measurement");
        this.lengthField = page.locator('input[name="h_length"]');
        this.widthField = page.locator('input[name="h_width"]');
        this.veryLowLeniencyRbtns = page.locator('input[name="leniency"]').first();
        this.lowLeniencyRbtn = page.locator('input[name="leniency"]').nth(1);
        this.mediumLeniencyRbtn = page.locator('input[name="leniency"]').nth(2);
        this.highLeniencyRbtn = page.locator('input[name="leniency"]').nth(3);
        this.veryHighLeniencyRbtn = page.locator('input[name="leniency"]').nth(4);
        this.fingertipGripTypeRbtn = page.locator('input[name="grip_type"]').first();
        this.clawGripTypeRbtn = page.locator('input[name="grip_type"]').nth(1);
        this.clawPlamGripTypeRbtn = page.locator('input[name="grip_type"]').nth(2);
        this.plamGripTypeRbtn = page.locator('input[name="grip_type"]').nth(3);
        this.bothShapeRbtn = page.locator('input[name="shape"]').first();
        this.symmetricalShapeRbtn = page.locator('input[name="shape"]').nth(1);
        this.asymmetricalShapeRbtn = page.locator('input[name="shape"]').nth(2);
        this.wirelessCB = page.locator('input[name="wireless"]');
        this.leftHandedCB = page.locator('input[name="lefthanded"]');
        this.numOfButtonsField = page.locator('#numButtons');
        this.searchBtn = page.locator('button', { name: 'SEARCH' });
        this.leniencyRButtons = [
            this.veryLowLeniencyRbtns,
            this.lowLeniencyRbtn,
            this.mediumLeniencyRbtn,
            this.highLeniencyRbtn,
            this.veryHighLeniencyRbtn
        ];
        this.gripTypeRButtons = [
            this.fingertipGripTypeRbtn,
            this.clawGripTypeRbtn,
            this.clawPlamGripTypeRbtn,
            this.plamGripTypeRbtn
        ];
        this.shapeRButtons = [
            this.bothShapeRbtn,
            this.symmetricalShapeRbtn,
            this.asymmetricalShapeRbtn
        ];
    }

    // Validated and checks if the defualt value are ste correctly for each field in the search form
    async validateSearchFormDefaultValues() {
        await expect(this.lengthField).toBeVisible({ timeout: 100000 });

        try {
            const measurementValue = await this.measurementDD.textContent();
            await expect(measurementValue.includes('CM')).toBeTruthy();
            await expect(this.lengthField).toHaveValue('');
            await expect(this.widthField).toHaveValue('');

            await this.validateRadioBtnChecked(this.leniencyRButtons, 2, 'Leniency');
            await this.validateRadioBtnChecked(this.gripTypeRButtons, 1, 'Grip Type');
            await this.validateRadioBtnChecked(this.shapeRButtons, 0, 'Shape');

            await expect(this.wirelessCB.isChecked()).toBeFalsy();
            await expect(this.leftHandedCB.isChecked()).toBeFalsy();

            await expect(this.numOfButtonsField).toHaveValue('');
        } catch (error) {
            console.error('Error during default values validation:', error);
            throw error;
        }
    }

    /**
     * fill the search form with provided paramrters and validates the form
     *errorrrrr  //Validates and performs a search mouse based on the provided criteria.
     * 
     * @param {string} measurement : the measurement option to select ('cm' or 'inches')
     * @param {string} length : the hand size length to fill the length field
     * @param {string} width : the hand size width to fill the width field
     * @param {number} leniencyIndex : the index of the lenincy option to select (0-4)
     * @param {number} gripTypeIndex : the index of the grip type option to select (0-3)
     * @param {number} shapeIndex : the index of the shape option to select (0-2)
     * @param {boolean} wireless : =false, whether to check the wireless checkbox
     * @param {boolean} lefthanded : =false, whether to check the lefthanded checkbox
     * @param {string | 'default'} numOfButtons : ='default', the number of buttons. Use 'default' for the default value.
     */
    async fillSearchForm(measurement, length, width, leniencyIndex, gripTypeIndex, shapeIndex, wireless = false, lefthanded = false, numOfButtons = 'default') {
        console.log(measurement, length, width, leniencyIndex, gripTypeIndex, shapeIndex, wireless, lefthanded, numOfButtons)
        try {
            // select the measurement option in the dropdown
            await this.measurementDD.selectOption({ value: measurement }, { timeout: 50000 });

            // fill in thr length and width fields 
            await this.lengthField.fill(length);
            await this.widthField.fill(width);

            // set leniency, grip type, and shape based on the provided indices
            await this.leniencyRButtons[leniencyIndex].check();
            await this.gripTypeRButtons[gripTypeIndex].check();
            await this.shapeRButtons[shapeIndex].check();

            // set wireless, lefthanded, and num of buttons if provided
            if (wireless) {
                await this.wirelessCB.check();
            }

            if (lefthanded) {
                await this.leftHandedCB.check();
            }

            if (numOfButtons !== 'default') {
                this.numOfButtonsField.fill(numOfButtons );
            }

            // validateSearchForm(measurement, length, width, leniencyIndex, gripTypeIndex, shapeIndex, wireless, lefthanded, numOfButtons);
            await this.validateFilledSearchForm(measurement, length, width, leniencyIndex, gripTypeIndex, shapeIndex, wireless, lefthanded, numOfButtons);

            // click the search button
            await this.searchBtn.click();
        } catch (error) {
            // handle and rethow any errors 
            console.error('Error during filling the form and validation:', error);
            throw error;
        }
    }

    // Selects the measurement option in the dropdown
    async selectMeasurementOption(option) {
        await this.measurementDD.selectedOption({ value: option });
    }
    /** 
        // Sets the wireless checkbox as checked
        async setWireless() {
            await this.wirelessCB.check();
        }
    
        // Sets the lefthanded checkbox as checked
        async setLeftHanded() {
            await this.leftHandedCB.check();
        }
    
        // Sets the number of buttons 
        async setNumberOfButtons(number) {
            this.numOfButtonsField.selectOption({ label: number });
        }
        */

    /**
     * Generate and shuffles the valid use cases array
     * @param {Array} array - the array of valid use cases
     * @returns {Promise<Array>} - a promise resolving to the shuffled arry of valid use cases
     * */
    async generateAndShuffleValidUseCase(array) {
        const newShuffleArray = shuffleArray(array);
        return newShuffleArray;
    }

    /**
     * Fills the search form with a shuffled arry of valid user cases
     * Interated throug 5 randomly selected cases, fills the form, and performs validation
     * @param {Array} array - the array of valid use cases 
     */
    async fillSearchShuffleValidUseCase(array) {
        try {
            // shuffle the arry of th use cases
            const shuffledValidUseCases = await this.generateAndShuffleValidUseCase(validUseCases);

            // iterate through 5 randomly selected cases
            const selectedUseCases = shuffledValidUseCases.slice(0, 5);
            for (const useCase of selectedUseCases) {
                const { measurement, length, width, leniencyIndex, gripTypeIndex, shapeIndex, wireless, lefthanded, numOfButtons } = useCase;

                // run the fill 
                await this.fillSearchForm(measurement, length, width, leniencyIndex, gripTypeIndex, shapeIndex, wireless, lefthanded, numOfButtons);
            }
        } catch (error) {
            console.error('Error during the form and validation:', error);
            throw error;
        }
    }

    /**
     * Validates that the search form parameters are set correctly after the form filled
     * @param {string} measurement - The expected measurement value.
     * @param {string} length - The expected length value.
     * @param {string} width - The expected width value.
     * @param {number} leniencyIndex - The expected index of the leniency option.
     * @param {number} gripTypeIndex - The expected index of the grip type option.
     * @param {number} shapeIndex - The expected index of the shape option.
     * @param {boolean} wireless - Whether wireless should be checked
     * @param {boolean} lefthanded - Whether lefthanded should be checked.
     * @param {string} numOfButtons - The expected number of buttons value.
     */
    async validateFilledSearchForm(measurement, length, width, leniencyIndex, gripTypeIndex, shapeIndex, wireless, lefthanded, numOfButtons) {
        try {
            await expect(await this.measurementDD.getAttribute('value')).toBe(measurement);
            await expect(await this.lengthField).toHaveValue(length);
            await expect(await this.widthField).toHaveValue(width);

            await this.validateRadioBtnChecked(this.leniencyRButtons, leniencyIndex, 'Leniency');
            await this.validateRadioBtnChecked(this.gripTypeRButtons, gripTypeIndex, 'Grip Type');
            await this.validateRadioBtnChecked(this.shapeRButtons, shapeIndex, 'Shape');

            if (wireless) {
                await expect(await this.wirelessCB.isChecked()).toBeTruthy();
            }

            if (lefthanded) {
                await expect(await this.leftHandedCB.isChecked()).toBeTruthy();
            }

            if (numOfButtons !== 'default') {
                await expect(await this.numOfButtonsField).toHaveValue(numOfButtons);
            }
        } catch (error) {
            if (error instanceof AssertionError) {
                throw new Error(`Validation after filling form failed ${error.message}`);
            }
            console.error('Error during validation:', error);
            throw error;
        }
    }

    // Validates that a rdio button is checked based on the index
    async validateRadioBtnChecked(radioButtonsList, index, fieldName) {
        const selectedRadioBtn = radioButtonsList[index];
        if (!selectedRadioBtn) {
            throw new Error(`${fieldName} index ${index} is out of range or the following checkbox isn't selected`);
        }
        await expect(await selectedRadioBtn.isChecked()).toBeTruthy();
    }
}

module.exports = SearchPage;

When I run the test, for some reason, every time in a different area on the test the following error displayed: Target page, context or browser has been closed and the page is closed

@mxschmitt
Copy link
Member

Yes your issue is different. In order to not mix up the issues, please file a new issue with code we can run locally. But from quickly looking at it, I can already observe the following:

@tongilcoto
Copy link
Author

Thanks @mxschmitt
Thanks for the effort and quick response

In any case the issue I am raising is not what you are fixing.
The issue is that when the test is failed it is doing what is not doing when it is passed. When the test fails the browser object gets closed and the second test reopens it. So it seems plausible that in both cases PW has to behave the same.

By the way, I have implemented a workaround by closing the page. I would explore closing the context in any case

@mxschmitt
Copy link
Member

The reason for that is that Playwright is restarting the worker when a test is failing, see here.

Closing as per above.

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

No branches or pull requests

3 participants