Search code examples
typescripttestingplaywright

playwright click function doesnt work in tests


I have a problem with pw click() function

this row is totally clickable on the page

    <TableRow
        key={i}
        whenClick={() => this.handleClickRow(el.id)}
        data-testId = {'warehousesRow'+`${i}`}
    >

but this test throws an exception, though the selector is fine in the previous line

        test('warehouses table rows clickable', async ({
        page,
        baseURL,
    }) => {
        await page.goto(baseURL as string)
        await checkKeyClock(page)
        await expect(page.url()).toContain(baseURL)
        await page.goto(baseURL as string + '/warehouses')
        const tableRow = page.locator('data-testid="warehousesRow1"')
        try {
                await tableRow.click();
        } catch (error) {
                console.error('Ошибка при клике на ряд таблицы:', error);
        }
        await expect(page.url()).toContain('view?id');
    })

the exception is

   steps: [],
    complete: [Function: complete],
    endWallTime: 1713781681655,
    error: {
      message: 'Error: locator.click: Test timeout of 30000ms exceeded.\n' +
        'Call log:\n' +
        `  \x1B[2m- waiting for locator('data-testid="warehousesRow1"')\x1B[22m\n`,
      stack: 'Error: locator.click: Test timeout of 30000ms exceeded.\n' +
        'Call log:\n' +
        `  \x1B[2m- waiting for locator('data-testid="warehousesRow1"')\x1B[22m\n` +
        '\n' +
        '    at /Users/anproskuryakova/frontend projects/ewms-ui/playwright/warehouses/wh-table.test.ts:32:21'
    }
  }
}

So it just looks like it is not clickable but i can click and navigate in the interface

await expect(tableRow).toBeVisible works just fine


Solution

  • For starters, I'd put the test id inside the component on a native element. Components are there to abstract away details, so the parent component doesn't need to (and shouldn't be able to) set this property, encroaching on the child's space.

    You can have duplicate test ids, and if you're appending an index to each one, you might as well use nth. nth isn't the greatest practice, since order isn't something that typically matters much to a user. So it's better to give that particular element a more specific test id, and/or select it by text, or some other distinguishing characteristic.

    Also, the standard style in CSS is kebab-case.

    'warehousesRow'+`${i}`
    

    can simply be:

    `warehousesRow${i}`
    

    HTML always lowercases properties, so data-testId renders as data-testid, especially run through React, which also lowercases properties (and converts className to class, etc). This should be selectable with getByTestId():

    import {expect, test} from "@playwright/test"; // ^1.42.1
    
    const html = `<!DOCTYPE html><html><body>
    <div data-testId="warehousesRow1">test</div>
    </body></html>`;
    
    test("warehousesRow exists", async ({page}) => {
      await page.setContent(html);
      await expect(page.getByTestId("warehousesRow1")).toBeVisible();
    });
    

    Better:

    const html = `<!DOCTYPE html><html><body>
    <div data-testid="warehouse-row">foo</div>
    <div data-testid="warehouse-row">bar</div>
    <div data-testid="warehouse-row">baz</div>
    </body></html>`;
    
    test("first warehouse row has text 'foo'", async ({page}) => {
      await page.setContent(html);
      await expect(page.getByTestId("warehouse-row").first()).toHaveText("foo");
    });
    

    You can also set a custom test id if necessary, but I don't recommend it. Change the original code (or ask your engineers) to use the standard test id if you can, preferably data-testid lowercased.

    If you must use CSS attribute syntax, the correct approach would be to use brackets (assuming your original version):

    await expect(page.locator('[data-testid="warehousesRow1"]')).toBeVisible();
    

    But getByTestId is much more readable, so prefer that almost always.