自定义选择器以匹配节点和所有后代

Custom selector to match a node and all descendants

这是 TestCafe 特有的问题。 我希望能够 select 元素基于元素本身或后代中任何地方的属性值。主要要求是能够做到这一点 无需异步调用 .

基本上,我正在尝试编写如下内容:

const findByTestName = (context: Selector, testName: string): Selector => {
    // if context itself matches [data-test~='${testName}'] return context;
    // return context.find('[data-test~='${testName}']');
};

可能吗?

编辑: 提供更多上下文:我本可以尝试使用 css 查询来解决它,例如 "#someid[data-test='testname'], #someid [data-test='testname']",但我正在尝试创建嵌套页面模型,如下所示:

class Page {
   constructor() {
     this.root = new Selector('[data-test="page"]');
     this.button = new Button(findByTestName(this.root, 'page__button'));
   }
}

class Button {
   constructor(context: Selector) {
     this.root = findByTestName(context, 'button');
     this.label = findByTestName(this.root, 'button__label');
   }
}

其中 DOM 配置可能是这样的:

<div id="page">
  <div data-test="page__button button">
    <span data-test="button__label" />
  </div>
</div>

或者这个。以实际布局为准

<div id="page">
  <div data-test="page__button">
    <div data-test="someotherstuff">
      <div data-test="button>
        <span data-test="button__label" />
      </div>
    </div>
  </div>
</div>

这样 Button pageModel 将是一个可重复使用的东西,可以应用于任何按钮。

您需要使用Selector.with方法。 看一个例子:

import { Selector } from 'testcafe';

fixture `Fixture`
    .page('./index.html');

const findByTestName = Selector(testName => {
    var checkElement = function (el, checkedAttrValue, result) {
        var testNameAttributeVale = el.getAttribute('data-test');

        if (testNameAttributeVale === checkedAttrValue)
            result.push(el);

        return result;
    };

    var rootElement = context();
    var result      = [];

    do {
        checkElement(rootElement);

        rootElement = rootElement.parentElement;
    }
    while(rootElement);


    return result;

}, { dependencies: { context: Selector('body') }});

test('test', async t => {
    const elms1 = await findByTestName('button');

    const elms2 = await findByTestName.with({
        dependencies: { context: Selector('div[data-test=button]')}
    })('someotherstuff');
});

您可以将选择器(甚至由 Selector.find 等过滤器函数生成)作为依赖项传递,以同步创建复杂的选择器:

import { Selector } from 'testcafe';


const findByTestName = (root, testName) => {
    const children = root.find(`[data-test=${testName}]`);

    const context = { root: root, children: children, testName: testName };

    return Selector(() => {
       const element = root();
       
       if (element.getAttribute('data-test') === testName)
           return el;
       
        return children();

    }, { dependencies: context });
}

fixture`Selector`.page`./test.html`;

test(`Dependencies`, async t => {
      const root = Selector('[data-test=page__button]');
      console.log(await findByTestName(root, "button__label").tagName);
});

请注意,您可以删除 findByTestName 函数中的所有常量,但 shorthand 属性 cannot be used to specify dependencies:

const findByTestName = (root, testName) => Selector(() => {
        //...
    }, { 
        dependencies: {
            root: root,
            children: root.find(...)
            //...
    }
);