assertion-before-screenshot.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. 'use strict'
  2. const assertionCommands = [
  3. // assertions
  4. 'should',
  5. 'and',
  6. 'contains',
  7. // retries until it gets something
  8. 'get',
  9. // not an assertion, but unlikely to require waiting for render
  10. 'scrollIntoView',
  11. 'scrollTo',
  12. ]
  13. module.exports = {
  14. meta: {
  15. docs: {
  16. description: 'Assert on the page state before taking a screenshot, so the screenshot is consistent',
  17. category: 'Possible Errors',
  18. recommended: false,
  19. },
  20. schema: [],
  21. messages: {
  22. unexpected: 'Make an assertion on the page state before taking a screenshot',
  23. },
  24. },
  25. create (context) {
  26. return {
  27. CallExpression (node) {
  28. if (isCallingCyScreenshot(node) && !isPreviousAnAssertion(node)) {
  29. context.report({ node, messageId: 'unexpected' })
  30. }
  31. },
  32. }
  33. },
  34. }
  35. function isRootCypress (node) {
  36. while (node.type === 'CallExpression') {
  37. if (node.callee.type !== 'MemberExpression') return false
  38. if (node.callee.object.type === 'Identifier' &&
  39. node.callee.object.name === 'cy') {
  40. return true
  41. }
  42. node = node.callee.object
  43. }
  44. return false
  45. }
  46. function getPreviousInChain (node) {
  47. return node.type === 'CallExpression' &&
  48. node.callee.type === 'MemberExpression' &&
  49. node.callee.object.type === 'CallExpression' &&
  50. node.callee.object.callee.type === 'MemberExpression' &&
  51. node.callee.object.callee.property.type === 'Identifier' &&
  52. node.callee.object.callee.property.name
  53. }
  54. function getCallExpressionCypressCommand (node) {
  55. return isRootCypress(node) &&
  56. node.callee.property.type === 'Identifier' &&
  57. node.callee.property.name
  58. }
  59. function isCallingCyScreenshot (node) {
  60. return getCallExpressionCypressCommand(node) === 'screenshot'
  61. }
  62. function getPreviousCypressCommand (node) {
  63. const previousInChain = getPreviousInChain(node)
  64. if (previousInChain) {
  65. return previousInChain
  66. }
  67. while (node.parent && !node.parent.body) {
  68. node = node.parent
  69. }
  70. if (!node.parent || !node.parent.body) return null
  71. const body = node.parent.body.type === 'BlockStatement' ? node.parent.body.body : node.parent.body
  72. const index = body.indexOf(node)
  73. // in the case of a function declaration it won't be found
  74. if (index < 0) return null
  75. if (index === 0) return getPreviousCypressCommand(node.parent)
  76. const previousStatement = body[index - 1]
  77. if (previousStatement.type !== 'ExpressionStatement' ||
  78. previousStatement.expression.type !== 'CallExpression') {
  79. return null
  80. }
  81. return getCallExpressionCypressCommand(previousStatement.expression)
  82. }
  83. function isPreviousAnAssertion (node) {
  84. const previousCypressCommand = getPreviousCypressCommand(node)
  85. return assertionCommands.indexOf(previousCypressCommand) >= 0
  86. }