Playwright locator.evaluateAll 如何返回使用节点 forEach 填充的对象数组

问题描述 投票:0回答:1

更新见下文

我想获取所有

<custom-tag>
节点的属性和
<custom-text>
类型或
<custom-number>

类型的节点子的innerText

devtools 中的简化版本是这样的


const headersMap = new Map();
var headers = Object.create(null);
document.querySelectorAll("h2").forEach(el => {
    
    headers[el.id] = el.innerText
    headersMap.set(el.id, el.innerText)    
});
console.log("h2 count > 2 === " , (headersMap.size>2))
console.table(Array.from(headersMap, ([name, value]) => ({ name, value })));

对于API测试页面这看起来像这样

这是我编剧的尝试

test('API Testing - H2 count greater than 2', async ({ }) => {     
    await test.step('API Testing - get titles', async() => {

        await page.goto('https://playwright.dev/docs/api-testing');
        
        const headersList = await page.locator('h2');
        var headersMap = await headersList.evaluateAll(node => {
            const headersMap = new Map();
            var headers = Object.create(null);
            headers[node.id] = node.innerText
            headersMap.set(node.id, node.innerText)    
            return headersMap;
        });
        await expect(headersMap.size).toBeGreaterThan(2);
        page.pause();
        
    });
});

但是它报告:

   Error: expect(received).toBeGreaterThan(expected)
   Matcher error: received value must be a number or bigint
   Received has value: undefined
      47 |             return headersMap;
      48 |         });
    > 49 |         await expect(headersMap.size).toBeGreaterThan(2);
         |                                       ^
      50 |         page.pause();

更新

ggorlen 的这个答案 已经有所帮助。

  • 但我仍然不明白为什么前面和后面的代码不起作用

我创建了这个 html 演示,它更接近我的用例。我想测试按钮价格上涨是否会在不同地方产生正确的值。

test("Demo - price increase", async ({page}) => {
    
  const url = "https://codepen.io/codemuggle/full/dygJLLV";
  await page.goto(url, {waitUntil: "domcontentloaded"});   
  // the const lineItems is undefined / empty <-- why?
  const lineItems = await page.locator("line-item").evaluateAll(nodes => {
    var lineItems = []
    // get values before price increase
    nodes.forEach( node => {
       lineItems.push({ 
         "price": node.querySelector("line-item-price").innerText,
         "qty": node.querySelector("input").value
       });
    });            
    return lineItems 
  });
  const expected = [{"price": 30, "qty": 8},
                    {"price": 21, "qty": 12}];        
  // compare values with expected
  expect(lineItems[0]).toEqual(expected[0]);      
  expect(lineItems[1]).toEqual(expected[1]);      
    
});

使用

npx playwright test /tests/so/eval.spec.js --ui
运行测试会在 ui-mode 输出窗口中导致此错误

 Error: expect(received).toEqual(expected) // deep equality

    Expected: {"price": 30, "qty": 8}
    Received: undefined

      57 |     const expected = [{"price": 30, "qty": 8},
      58 |                       {"price": 21, "qty": 12}];        
    > 59 |     expect(lineItems[0]).toEqual(expected[0]);      
         |                          ^

问题

  • 如何创建一个用 forEach 填充并返回的对象?
  • my_locator.evaluateAll()
    返回地图必须改变什么?
  • 为什么
    const lineItems
    未定义?
  • 我更新的代码有什么问题 - 必须更改什么才能修复它?
javascript eval playwright
1个回答
1
投票

当你使用

locator.evaluateAll()
时,你会在回调中得到一个数组。我会调用参数
nodes
而不是
node
所以你很清楚你需要迭代它。

第二个问题:地图不容易序列化。试试看:

const m = new Map();
m.set("hello", "world");
console.log(m.get("hello")); // => "world"
console.log(JSON.stringify(m)); // => {}

传入和传出

evaluate
调用的所有数据都将被反序列化/序列化,这将破坏您的地图。除非你有一些令人信服的理由使用地图,否则我只会使用一个普通的旧对象。

这里有一个选项:

import {expect, test} from "@playwright/test"; // ^1.30.0

test("playwright docs page has correct headers", async ({page}) => {
  const url = "https://playwright.dev/docs/api-testing";
  await page.goto(url, {waitUntil: "domcontentloaded"});
  const headers = await page.locator("h2").evaluateAll(nodes =>
    Object.fromEntries(
      nodes.map(node => [node.id, node.textContent.trim()])
    )
  );
  const expected = {
    "writing-api-test": "Writing API Test​",
    "using-request-context": "Using request context​",
    "sending-api-requests-from-ui-tests": "Sending API requests from UI tests​",
    "reusing-authentication-state": "Reusing authentication state​",
    "context-request-vs-global-request": "Context request vs global request​",
  };
  expect(headers).toEqual(expected);
});

请注意,您不需要

await
定位器定义,只需一个动作。通常使用
.textContent
而不是
.innerText
。修剪您的 HTML 字符串以规范化它们。注意那些标题中有趣的
​&ZeroWidthSpace;
s(考虑只抓取文本节点并忽略
<a>
)。无需
await
非定位器断言。

总而言之,上述方法并不是最佳实践,因为它没有使用 web first 断言。我会重写它以直接在定位器上使用

.toHaveText()

await expect(page.locator("h2")).toHaveText([
  "Writing API Test​",
  "Using request context​",
  "Sending API requests from UI tests​",
  "Reusing authentication state​",
  "Context request vs global request​",
]);

ID 可以单独测试,而且可能真的没有那么重要。您可以 pollretry 等待谓词为真作为最后的手段,但如果您确定它们始终静态地显示在页面上,那么打破 web-first 规则可能是可以的。

© www.soinside.com 2019 - 2024. All rights reserved.