In testcafe, can you create a custom test action that chains custom calls until it returns a TestControllerPromise?

huangapple go评论58阅读模式
英文:

In testcafe, can you create a custom test action that chains custom calls until it returns a TestControllerPromise?

问题

这是我希望我的testcafe自定义测试操作看起来的方式:

  await t
    .click(Selector(/* ... */))
    .customActions
      .toFindOneDialogElement() // 返回我的代码
      .withExactTitle("My Title") // 返回我的代码
      .and() // 返回我的代码
      .withExactBodyText("My content") // 返回我的代码
      .done() // 返回TestControllerPromise
    .click(Selector(/* ... */))

文档中提到:

如果您想在操作链中包含自定义操作,请确保该函数不返回值。

但是在我的API中,我希望在调用done之前返回自定义值。这可以实现吗?

这是我的尝试。我将从类型定义开始:

import "testcafe";

declare global {
  interface CustomActions {
    toFindOneDialogElement: () => Promise<BuilderMatcher & BuilderDone>;
  }
}

interface BuilderMatcher {
  withExactTitle: (
    exactTitle: string
  ) => Promise<BuildMatcherBooleanOperator & BuilderDone>;
  withExactBodyText: (
    exactBodyText: string
  ) => Promise<BuildMatcherBooleanOperator & BuilderDone>;
}

interface BuildMatcherBooleanOperator {
  and: () => Promise<BuilderMatcher>;
}

interface BuilderDone {
  done: () => TestControllerPromise;
}

这是我在.testcaferc"customActions"属性中的实现:

{
  async toFindOneDialogElement() {
    const self = this;
    let exactTitle = undefined;
    let exactBodyText = undefined;

    async function done() {
      exactTitle !== undefined &&
        (await self.expect(Selector("...").innerText).eql(exactTitle));

      exactBodyText !== undefined &&
        (await self.expect(Selector(`...`).innerText).eql(exactBodyText));

      return self;
    }

    async function and() {
      return builderMatcher;
    }

    const builderMatcher = {
      withExactTitle: async function (exactTitleParam) {
        exactTitle = exactTitleParam;
        return { done, and };
      },
      withExactBodyText: async function (exactBodyTextParam) {
        exactBodyText = exactBodyTextParam;
        return { done, and };
      },
      done,
    };

    return builderMatcher;
  },
}

当我尝试像第一个代码示例那样使用我的自定义测试操作时,它无法编译,因为第一个函数返回一个Promise<T>,必须使用await。修复方法很简单,但它会破坏API设计:它不再是链式的。内置的testcafe操作可以在不使用await的情况下链接。我的自定义操作API是否也可以这样做?

英文:

Here's how I'd like my testcafe custom test action to look:

  await t
    .click(Selector(/* ... */))
    .customActions
      .toFindOneDialogElement() // returns my code
      .withExactTitle(&quot;My Title&quot;) // returns my code
      .and() // returns my code
      .withExactBodyText(&quot;My content&quot;) // returns my code
      .done() // returns TestControllerPromise
    .click(Selector(/* ... */))

The documentation says:

> If you want to include custom actions in action chains, make sure the function does not return a value.

But in my API, I want to return a custom value until done is called. Can this be done?

Here is my attempt. I'll start with the type definitions:

import &quot;testcafe&quot;;

declare global {
  interface CustomActions {
    toFindOneDialogElement: () =&gt; Promise&lt;BuilderMatcher &amp; BuilderDone&gt;;
  }
}

interface BuilderMatcher {
  withExactTitle: (
    exactTitle: string
  ) =&gt; Promise&lt;BuildMatcherBooleanOperator &amp; BuilderDone&gt;;
  withExactBodyText: (
    exactBodyText: string
  ) =&gt; Promise&lt;BuildMatcherBooleanOperator &amp; BuilderDone&gt;;
}

interface BuildMatcherBooleanOperator {
  and: () =&gt; Promise&lt;BuilderMatcher&gt;;
}

interface BuilderDone {
  done: () =&gt; TestControllerPromise;
}

Here is my implementation inside the .testcaferc &quot;customActions&quot; property:

{
  async toFindOneDialogElement() {
    const self = this;
    let exactTitle = undefined;
    let exactBodyText = undefined;

    async function done() {
      exactTitle !== undefined &amp;&amp;
        (await self.expect(Selector(&quot;...&quot;).innerText).eql(exactTitle));

      exactBodyText !== undefined &amp;&amp;
        (await self.expect(Selector(`...`).innerText).eql(exactBodyText));

      return self;
    }

    async function and() {
      return builderMatcher;
    }

    const builderMatcher = {
      withExactTitle: async function (exactTitleParam) {
        exactTitle = exactTitleParam;
        return { done, and };
      },
      withExactBodyText: async function (exactBodyTextParam) {
        exactBodyText = exactBodyTextParam;
        return { done, and };
      },
      done,
    };

    return builderMatcher;
  },
}

When I try to use my custom test action like the first code sample, it does not compile because that first function returns a Promise&lt;T&gt; and must be awaited. The fix is straightforward but it ruins the API design: it no longer chains.

The built-in testcafe actions can chain without awaiting each call. Is there a way my custom action API can too?

答案1

得分: 1

您目前的JavaScript实现按预期工作。我们正在等待自定义操作的异步函数完成。如果此函数返回某个值,我们将将此值作为此函数调用的结果返回。因此,函数的返回类型是Promise<value>。如果没有返回任何值,我们将返回TestControllerPromise对象,这是我们的自定义对象,其中包含所有TestCafe操作。这就是为什么您不能以这种方式链接您的代码的原因。目前,您可以按以下方式更改您的链:

 await t.customActions
        .toFindOneDialogElement()
        .then(builder => builder.withExactTitle('More information...'))
        .then(builder => builder.and())
        .then(builder => builder.withExactBodyText('More information...'))
        .then(builder => builder.done())

此外,您需要更新选择器,因为它们无法在此情况下获取绑定的testRun:

Selector('...').with({ boundTestRun: self }).innerText

但在这种情况下,在toFindOneDialogElement调用之后的内置链将丢失。如果您希望内部的TestCafe逻辑使这些方法可链接,您需要将它们放在单独的操作中,并通过customActions属性调用它们,如下所示:

 await t
        .click('...')
        .customActions.toFindOneDialogElement()
        .customActions.withExactTitle('More information...')
        .customActions.and()
        .customActions.withExactBodyText('More information...')
        .customActions.done()
        .click('...')
英文:

Your current implementation works as expected in terms of JavaScript. We are awaiting the custom action async function to be finished. If this function returns some value, we will return this value as a result of this function call. So the function return type is Promise&lt;value&gt;. If no value has been returned, we will return TestControllerPromise object which is our custom object that has all the TestCafe actions. That is why you cannot chain your code this way. Currently, you can change your chain as follows:

 await t.customActions
        .toFindOneDialogElement()
        .then(builder =&gt; builder.withExactTitle(&#39;More information...&#39;))
        .then(builder =&gt; builder.and())
        .then(builder =&gt; builder.withExactBodyText(&#39;More information...&#39;))
        .then(builder =&gt; builder.done())

Also, you need to update your selectors since they are not able to get a bound testRun in this case:

Selector(&#39;...&#39;).with({ boundTestRun: self }).innerText

But in this case, the built-in chain after the toFindOneDialogElement call will be lost. If you would like internal TestCafe logic to make these methods chainable, you need to put them in separate actions and call them through the customActions property as follows:

 await t
        .click(&#39;...&#39;)
        .customActions.toFindOneDialogElement()
        .customActions.withExactTitle(&#39;More information...&#39;)
        .customActions.and()
        .customActions.withExactBodyText(&#39;More information...&#39;)
        .customActions.done()
        .click(&#39;...&#39;)


huangapple
  • 本文由 发表于 2023年7月11日 07:44:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76657937.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定