【问题标题】:How to execute one element then and then another in protractor如何在量角器中执行一个元素,然后执行另一个元素
【发布时间】:2019-05-17 17:13:27
【问题描述】:

以下代码随机运行,有时它可以正常工作,有时它会抛出这样的错误Stale Element Reference Exception

我想要的是我想先在下面执行这个

element(by.id('FiltItemTransDocNo')).sendKeys(grno);

在上面之后我希望它在下面执行

element.all(by.name('chkGrd')).first().click();

我试过这种方法,但它似乎不起作用

element(by.id('FiltItemTransDocNo')).sendKeys(grno).then(function(el){
     element.all(by.name('chkGrd')).first().click();
});

帮我解决这个问题

我已附上图片 我正在向采购申请字段发送密钥,根据我想出的结果将只显示一个结果,我想点击 如果我为可见性设置条件,它将始终为true,那么我将导致同样的问题

【问题讨论】:

  • 我认为您的.click() 是一个需要等待的承诺(例如:await element.all(by.name('chkGrd')).first().click();
  • 它将需要异步 @HaunsTM
  • @JeffC 是的,我知道,这是因为 dom 元素不存在

标签: javascript selenium async-await protractor automated-tests


【解决方案1】:

关于sendKeyse1 的简要说明

快速注意sendKeys 不返回 WebElement 或 ElementFinder。这意味着上例中的 e1 可能是未定义的。

陈旧元素和 DOM 变化

关于假设的快速说明:答案假设将文本发送到过滤器会改变屏幕上的行数或元素数。如果在您发送文本后屏幕上的元素数量相同,那么这将不起作用。我会看看下面 Florent 对过时参考错误的评论。

陈旧的元素通常在 DOM 发生变化时发生。如果你在 Angular 中使用一些结构性指令,如果你使用 *ngFor 或 *ngIf,DOM 将会改变。我的猜测是,在您过滤元素之后,您将在 DOM 根据您的过滤器实际更改期间或之前获取 DOM 中的元素。这将导致过时的引用 Web 元素。在下面的示例中,我使用 async / await 并关闭了控制流。

过滤期间的显式等待

您可以显式设置睡眠,以便在您调用单击第一个元素之前更新 DOM。这可能会导致潜在的不稳定测试,因为根据您将运行的环境,超时是未知的。

it('should do something', async () => {
  const filterItem = element(by.id('FiltItemTransDocNo'));
  await filterItem.sendKeys(grno);
  await browser.sleep(1000);  // or some other waits
  await element.all(by.name('chkGrd')).first().click();
});

比较 element.all 的行

或者,您可以对点击前后的element.all 项目的数量进行比较检查,并仅在内容更新时继续。

it('should do something', async () => {
  const filterItem = element(by.id('FiltItemTransDocNo'));
  const table = element.all(by.name('chkGrd'));
  const length = await table.count();

  await filterItem.sendKeys(grno);

  // After the filter keys are sent, check to see if the current table
  // count is not equal to the `table.count()`.
  let updated = false;
  await browser.wait(async () => {
    updated = length !== await table.count();
    return updated;
  }, 5000);

  // So if we use the entire 5 seconds and the count has not changed,
  // we should probably fail before clicking on stuff.
  expect(updated).toBeTruthy();

  // now we can click on the next element.
  await element.all(by.name('chkGrd')).first().click();
});

为什么调用length !== await table.count() 有效?这是因为表格代表了获取 Web 元素的承诺。当您调用count 方法时,它首先通过解析Web 元素来执行操作。如果 DOM 发生变化,这可能会有所不同。然后,我们将当前计数与前一个计数进行比较。

确保您使用的是 async / await

在您的配置文件中,您需要指定您已脱离控制流:

exports.config = {
    // In this case, I plan to use a selenium standalone server
    // at http://127.0.0.1:4444/wd/hub. You could also use other 
    // methods like direct connect or starting it up with 'local'.
    seleniumAddress: 'http://127.0.0.1:4444/wd/hub',

    // Required flag to tell Protractor you do not want to use the
    // control flow. This means that you will have to either chain
    // your promises or async / await your actions. Historically
    // jasminewd node module would resolve promises for you. This
    // package will no longer be used in future releases since the
    // control flow is deprecated.
    SELENIUM_PROMISE_MANAGER: false,

    // The rest of your config...
}

希望对您有所帮助。

【讨论】:

  • 好的 1 个快速问题是没有任何方法可以绑定两个 DOM 元素并以这样的方式链接它们,以便一个接一个地执行 1
  • 大多数事情都很好,除了 Angular 结构指令最终会改变 DOM(这是我对上述答案的假设)。用户键入后,可能会发生一些超时,表明用户已完成键入。然后应用程序将根据输入过滤掉。除非您检查,否则无法保证您拥有正确的 element.all。
  • 另外,是的,您需要使用 async / await。如果你不是,你应该考虑一下......因为没有异步/等待承诺,下一个版本将无法工作。
  • 这似乎使用 1 个浏览器实例也会尝试使用多个实例谢谢您的帮助
  • 当刷新时间不确定时,永远不应该使用静态延迟来等待。并且通过等待更新元素的数量,您可能会遇到使用相同数量的元素更新内容的情况,这在分页中很常见。更好的方法是等待以前的 WebElement/reference 过时。
【解决方案2】:

对于过时的元素引用有几种解决方案。

第一:

    let filterItem = element(by.id('FiltItemTransDocNo'));
    browser.wait(ExpectedConditions.visibilityOf(filterItem), 5000, 'Element is not visible.');
    filterItem.sendKeys('some random text');

    let elementToClick = element.all(by.name('chkGrd')).first();
    browser.wait(ExpectedConditions.elementToBeClickable(elementToClick), 5000, 'Element is not clickable.');
    elementToClick.click();

您也可以将它们链接起来:

browser.wait(ExpectedConditions.visibilityOf(filterItem), 5000, 'Element is not visible.').then( () => {
                filterItem.sendKeys('some random text');

                browser.wait(ExpectedConditions.elementToBeClickable(elementToClick), 5000, 'Element is not clickable.').then( () => {
                    elementToClick.click();
                });
            });

或者第二种方法,刷新过时错误的元素:

let filterItem = element(by.id('FiltItemTransDocNo'));

            try {
                filterItem.sendKeys('some random text');
            } catch (e) {
                if (e instanceof StaleElementReferenceError) {
                    filterItem = element(by.id('FiltItemTransDocNo'));
                    filterItem.sendKeys('text');
                }
            }

【讨论】:

  • 是的,因为在我的情况下可见性将返回 true 元素始终存在,因此它不会等待
  • 我想要的是以 sendkeys 先执行然后单击执行的方式链接它们
  • 当你想点击元素时,条件可见性可以为真,但元素不可点击,所以有'elementToBeClickable();'这样的条件
  • 发送key但不点击ele
  • 如果我把 browser.driver.sleep(500); 这个然后它工作,但它似乎不适合我
【解决方案3】:

正如我在answer to another question 中解释的那样,Protractor 及其使用的工具都在更改为使用原生 JavaScript 承诺,期望每个人都将他们的代码迁移到完全异步的编码风格。不幸的是,它们并不容易,因为您不能将旧样式代码与新样式混合在一起。所以首先,确保你的整个套件都是异步风格的,并且你已经在你的配置中设置了SELENIUM_PROMISE_MANAGER: false

响应sendKeys 会发生什么?如果这触发了 AJAX 调用并且对该调用的响应更改了 DOM,那么您可能必须按照@cnishina 的建议进行操作并轮询 DOM 以等待更改生效。但是,如果只发生 Angular 客户端更改,那么这段代码应该可以工作:

it('should do something', async () => {
  await element(by.id('FiltItemTransDocNo')).sendKeys(grno);
  await element.all(by.name('chkGrd')).first().click();
});

element 的两个调用都与 Angular 同步,以确保在定位器继续之前完成 Angular 更新,因此就 Angular 而言,您应该没问题。

【讨论】:

    猜你喜欢
    • 2020-04-11
    • 2016-01-27
    • 2011-01-15
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多