【问题标题】:cypress xhr request retry ontimeoutcypress xhr 请求重试 ontimeout
【发布时间】:2020-12-25 02:33:12
【问题描述】:

如果超时,我正在尝试发出以下XHR 请求重试。如果我删除cy.wait('@formRequest'),似乎测试不会停止以正确等待响应。但是如果我留下cy.wait,如果请求超时,测试将失败。所以我尝试向cy.request 调用(显示在第二个代码块中)添加一些回调(ontimeout),但这似乎也不起作用。如果响应不是 200 或者根本没有收到响应,我知道如何让这个 cypress 命令重试吗??

我对此很陌生,因此不胜感激。

提前致谢!

// this works, but doesn't handle timeout
Cypress.Commands.add('form_request', (url, formData) => {
  function recursive_request (url, formData, attempt) {
    let xhr
    const maxRetry = 3

    if (attempt > maxRetry) {
      throw new Error(`form_request() max retry reached, ${maxRetry}`)
    }

    if (attempt !== 0) {
      cy.log(`Backing off retry. Attempt: ${attempt}/${maxRetry}.`)
      cy.wait(30000)
    }

    return cy
      .server()
      .route('POST', url)
      .as('formRequest')
      .window()
      .then((win) => {
        xhr = new win.XMLHttpRequest()
        xhr.open('POST', url)
        xhr.send(formData)
      })
      .wait('@formRequest')
      .then((resp) => {
        if (xhr.status !== 200) {
          return recursive_request(url, formData, attempt + 1)
        }

        return resp
      })
  }

  return recursive_request(url, formData, 0)
})
Cypress.Commands.add('form_request', (url, formData) => {
  function recursive_request (url, formData, attempt) {
    let xhr
    const maxRetry = 3

    if (attempt > maxRetry) {
      throw new Error(`form_request() max retry reached, ${maxRetry}`)
    }

    if (attempt !== 0) {
      cy.log(`Backing off retry. Attempt: ${attempt}/${maxRetry}.`)
      cy.wait(30000)
    }

    return cy
      .server()
      // .route('POST', url)
      .route({
        'method': 'POST',
        'url': url
      }, {
        ontimeout: (xhr) => {
            return new Cypress.Promise  ((resolve) => {
              resolve(recursive_request(url, formData, attempt + 1))
            })
            
        },
        onreadystatechange: (xhr) => {
          if (xhr.status !== 200 && xhr.readyState === 4) {
            return new Cypress.Promise  ((resolve) => {
              resolve(recursive_request(url, formData, attempt + 1))
            })
          }
        }
      })
      .as('formRequest')
      .window()
      .then((win) => {
        xhr = new win.XMLHttpRequest()
        xhr.open('POST', url)
        xhr.send(formData)
      })
      .wait('@formRequest')
      .then((resp) => {
        if (xhr.status !== 200) {
          return recursive_request(url, formData, attempt + 1)
        }

        return resp
      })
  }

  return recursive_request(url, formData, 0)
})

编辑: 我尝试制作以下自定义 cypress 命令。但是,我收到非法调用错误。查看有关 jQuery.ajax() 的文档,我不清楚它的非法之处。

我还有几个问题... #1。传入 {timeout: 30000} 对象是如何工作的?这个叫什么?我对 Promise 链的基本理解是,你传入一些处理函数来使用之前调用产生的任何东西。没见过.then(arg, function () {...})这个图案,这叫什么?

#2。 cy.log(...) 会在这里工作吗?我怀疑它不会因为那会混合同步/异步代码,对吗? (对不起,我现在会自己玩,但我无法让它通过非法调用错误)。 const log = Cypress.log(...) 会在这里同步工作吗?

  527 |       }
  528 | 
> 529 |       Cypress.$.ajax({
      |                 ^
  530 |         url,
  531 |         method: 'POST',
  532 |         data: formData,
Cypress.Commands.add('form_request', (url, formData) => {
  const waits = { request: 2000, retry: 10000 }
  const maxRetry = 3

  function recursive_request(url, formData, attempt = 0) {

    return new Cypress.Promise((resolve) => {
      if (attempt > maxRetry) {
        throw new Error(`form_request() max retry reached, ${maxRetry}`)
      }

      const retry = (reason) => {
        console.log(`${reason} - retrying in ${waits.retry}`) // #2
        return setTimeout(() => {
          recursive_request(url, formData, attempt + 1)
            .then(data => resolve(data)); // resolve from recursive result
        }, waits.retry)
      }

      Cypress.$.ajax({
        url,
        method: 'POST',
        data: formData,
        timeout: waits.request,
      })
        .done(function (data, textStatus, jqXHR) {
          if (textStatus !== 'success') {
            retry('Status not success')
          }
          resolve(data)
        })
        .catch(function (jqXHR, textStatus, errorThrown) {
          retry(errorThrown)
        })
    })
  }

  cy.wrap(null)
    .then({ timeout: 30000 }, // #1
      () => {
        return recursive_request(url, formData)
      })
    .then((result) => {
      return result
    })
})

【问题讨论】:

  • 这能回答你的问题吗? stackoverflow.com/a/54081260/1727948
  • 尝试将 ontimeout 回调移动到xhr 对象,参见XMLHttpRequest: timeout event
  • @konekoya ,不幸的是没有,因为我正在尝试处理没有回应的情况。
  • 您的数据不是代码所暗示的 formData。设置contentType: false - contentType (default: 'application/x-www-form-urlencoded; charset=UTF-8') ... 传递 false 告诉 jQuery 不要设置任何内容类型标头我>。还有processData: false - processData(默认:true),如果要发送...其他未处理的数据,请将此选项设置为false。
  • 啊!解决了非法调用问题。谢谢!

标签: javascript xmlhttprequest cypress


【解决方案1】:

测试级重试

我会看看test-retries (Cypress v 5.0+) 来处理超时问题,

const waitTime = 1000; // shortened for demo 

Cypress.Commands.add('form_request', (url, formData) => {

  return cy
    .request({
      method: 'POST', 
      url,
      body: formData,
      timeout: waitTime
    })
    .then((resp) => {
      if (resp.status !== 200) {
        throw new Error(`Status not 200`)
      }
      return resp
    })
})

let attempts = 0;

it('repeats', { retries: 3 }, () => {

  const wait = attempts ? waitTime : 0;  // initial wait = 0
  const url = attempts < 2
    ? 'http://nothing'                   // initially use a non-reachable url
    : 'http://example.com';              // 3rd attempt succeeds
  attempts++;
  cy.wait(wait).form_request(url, { name: 'Fred' })

})

请求级重试

如果您想在请求级别(而不是测试级别)重试,那么 @Ackroydd 是正确的 - 您应该放弃 cy.route(),因为简而言之,Cypress 命令不是为捕获失败而构建的。

这是一个使用jquery.ajax 的基本示例。您可能会使用axios.retry 之类的东西获得更简洁的功能。

有关使用的赛普拉斯模式,请参阅Waiting for promises

递归请求

const waits = { request: 2000, retry: 10000 } 
const maxRetry = 3

function recursive_request(url, formData, attempt = 0) {

  return new Cypress.Promise ((resolve) => {
    if (attempt > maxRetry) {
      throw new Error(`form_request() max retry reached, ${maxRetry}`)
    }
  
    const retry = (reason) => {
      console.log(`${reason} - retrying in ${waits.retry}`);
      return setTimeout(() => {
        recursive_request(url, formData, attempt + 1)
          .then(data => resolve(data)); // resolve from recursive result
      }, waits.retry)
    }
    
    Cypress.$.ajax({
      url,
      method: 'POST',
      data: formData,
      timeout: waits.request,
    })
    .done(function(data, textStatus, jqXHR) {
      if (textStatus !== 'success') {
        retry('Status not success')
      }
      resolve(data)
    })
    .catch(function(jqXHR, textStatus, errorThrown) {
      retry(errorThrown)
    })
  })
}

测试

it('retries form post', () => {

  cy.wrap(null)
    .then(
      { timeout: 30000 },   // timeout must be above maximum expected, 
                            // i.e maxRetry * waits.retry
      () => recursive_request('http://localhost:3000', { name: 'Fred' })
    )
    .then(result => console.log(result))

})

用于测试的不稳定服务器

const express = require("express");
const app = express();
const port = 3000;

const obj = {
  name: 'Fred'
};

let attempt = 0;

app.post("*", (req, res) => {
  if (attempt >= 3) {
    attempt = 0;   // reset to allow multiple test runs
    res.status(201).json(obj)
  }
});

app.listen(port, () => {
  console.log(`Example app listening at http://localhost:${port}`);
});

【讨论】:

  • 嘿,理查德,感谢您的回复!使用测试重试似乎是个好主意,我猜根据你的建议,你不知道有什么方法可以在 cypress 命令本身中处理重试?我问的原因是 form_request 用于发送 blob 作为更长的端到端测试的一部分。我可以隔离测试执行的那部分,其中 form_request 被称为新测试,但不希望(如果可能)。至于让整个测试重复 - 这是可能的,但会导致其他问题(后端资源被锤击,宁愿只在必要时重试)
  • 嗨,Richard,哇,感谢您提供非常详细的代码。我会研究你的答案并尝试一下,看看它是否有效。再次感谢您!
  • 嗨,Richard,我已尝试调整您的 cmets 以适应一个自定义 cypress 命令。原因是调用 cy.form_request(...) 将 blob 发送到端点。我还有一些关于您提供的代码的其他问题,非常感谢您抽出宝贵的时间!我正在学习很多关于这些东西是如何工作的。 (我已经编辑了主要的原始问题)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-03-07
  • 2021-01-09
  • 2019-08-10
  • 2019-04-02
  • 2023-03-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多