cypress-on-rails 1.17.0 → 1.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (337) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/claude-code-review.yml +57 -0
  3. data/.github/workflows/claude.yml +50 -0
  4. data/.github/workflows/ruby.yml +14 -14
  5. data/CHANGELOG.md +319 -98
  6. data/README.md +271 -23
  7. data/RELEASING.md +200 -0
  8. data/Rakefile +1 -4
  9. data/cypress-on-rails.gemspec +1 -0
  10. data/docs/BEST_PRACTICES.md +678 -0
  11. data/docs/DX_IMPROVEMENTS.md +163 -0
  12. data/docs/PLAYWRIGHT_GUIDE.md +554 -0
  13. data/docs/RELEASE.md +124 -0
  14. data/docs/TROUBLESHOOTING.md +351 -0
  15. data/docs/VCR_GUIDE.md +499 -0
  16. data/docs/authentication.md +30 -0
  17. data/docs/factory_bot_associations.md +14 -0
  18. data/lib/cypress_on_rails/configuration.rb +29 -0
  19. data/lib/cypress_on_rails/railtie.rb +17 -2
  20. data/lib/cypress_on_rails/server.rb +197 -0
  21. data/lib/cypress_on_rails/state_reset_middleware.rb +58 -0
  22. data/lib/cypress_on_rails/vcr/insert_eject_middleware.rb +75 -0
  23. data/lib/cypress_on_rails/vcr/middleware_helpers.rb +51 -0
  24. data/lib/cypress_on_rails/vcr/use_cassette_middleware.rb +56 -0
  25. data/lib/cypress_on_rails/version.rb +1 -1
  26. data/lib/generators/cypress_on_rails/templates/config/initializers/cypress_on_rails.rb.erb +20 -0
  27. data/lib/generators/cypress_on_rails/templates/spec/cypress/e2e/rails_examples/using_factory_bot.cy.js +2 -2
  28. data/lib/generators/cypress_on_rails/templates/spec/cypress/e2e/rails_examples/using_scenarios.cy.js +1 -1
  29. data/lib/generators/cypress_on_rails/templates/spec/cypress/support/commands.js +22 -0
  30. data/lib/generators/cypress_on_rails/templates/spec/cypress/support/on-rails.js +2 -1
  31. data/lib/generators/cypress_on_rails/templates/spec/e2e/e2e_helper.rb.erb +0 -5
  32. data/lib/tasks/cypress.rake +33 -0
  33. data/rakelib/release.rake +80 -0
  34. data/rakelib/task_helpers.rb +23 -0
  35. data/rakelib/update_changelog.rake +63 -0
  36. data/spec/cypress_on_rails/configuration_spec.rb +4 -1
  37. data/spec/cypress_on_rails/vcr/insert_eject_middleware_spec.rb +177 -0
  38. data/spec/cypress_on_rails/vcr/use_cassette_middleware_spec.rb +68 -0
  39. data/specs_e2e/rails_6_1/.gitattributes +10 -0
  40. data/specs_e2e/rails_6_1/Gemfile +20 -0
  41. data/specs_e2e/{rails_5_2 → rails_6_1}/Rakefile +1 -1
  42. data/specs_e2e/rails_6_1/app/assets/stylesheets/application.css +15 -0
  43. data/specs_e2e/rails_6_1/app/jobs/application_job.rb +7 -0
  44. data/specs_e2e/{rails_5_2 → rails_6_1}/app/views/layouts/application.html.erb +1 -1
  45. data/specs_e2e/rails_6_1/bin/bundle +114 -0
  46. data/specs_e2e/rails_6_1/bin/rails +5 -0
  47. data/specs_e2e/rails_6_1/bin/rake +5 -0
  48. data/specs_e2e/{rails_5_2 → rails_6_1}/bin/setup +15 -4
  49. data/specs_e2e/rails_6_1/bin/spring +14 -0
  50. data/specs_e2e/rails_6_1/bin/yarn +17 -0
  51. data/specs_e2e/rails_6_1/config/application.rb +34 -0
  52. data/specs_e2e/rails_6_1/config/boot.rb +4 -0
  53. data/specs_e2e/rails_6_1/config/cable.yml +10 -0
  54. data/specs_e2e/rails_6_1/config/credentials.yml.enc +1 -0
  55. data/specs_e2e/{rails_5_2 → rails_6_1}/config/database.yml +7 -0
  56. data/specs_e2e/{rails_5_2 → rails_6_1}/config/environment.rb +1 -1
  57. data/specs_e2e/{rails_5_2 → rails_6_1}/config/environments/development.rb +22 -5
  58. data/specs_e2e/rails_6_1/config/environments/production.rb +120 -0
  59. data/specs_e2e/{rails_5_2 → rails_6_1}/config/environments/test.rb +24 -7
  60. data/specs_e2e/rails_6_1/config/initializers/backtrace_silencers.rb +8 -0
  61. data/specs_e2e/{rails_5_2 → rails_6_1}/config/initializers/content_security_policy.rb +5 -0
  62. data/specs_e2e/{rails_4_2 → rails_6_1}/config/initializers/filter_parameter_logging.rb +3 -1
  63. data/specs_e2e/rails_6_1/config/initializers/permissions_policy.rb +11 -0
  64. data/specs_e2e/{rails_3_2 → rails_6_1}/config/initializers/wrap_parameters.rb +5 -1
  65. data/specs_e2e/{rails_5_2 → rails_6_1}/config/locales/en.yml +1 -1
  66. data/specs_e2e/rails_6_1/config/master.key +1 -0
  67. data/specs_e2e/rails_6_1/config/puma.rb +43 -0
  68. data/specs_e2e/rails_6_1/config/storage.yml +34 -0
  69. data/specs_e2e/{rails_5_2 → rails_6_1}/config.ru +2 -1
  70. data/specs_e2e/{rails_5_2 → rails_6_1}/db/migrate/20180621085832_create_posts.rb +1 -1
  71. data/specs_e2e/rails_6_1/package.json +8 -0
  72. data/specs_e2e/rails_6_1/playwright-report/index.html +71 -0
  73. data/specs_e2e/rails_6_1/public/robots.txt +1 -0
  74. data/specs_e2e/rails_6_1/test-results/.last-run.json +4 -0
  75. data/specs_e2e/{rails_4_2 → rails_6_1}/test.sh +22 -15
  76. data/specs_e2e/rails_7_2/.gitattributes +9 -0
  77. data/specs_e2e/rails_7_2/.gitignore +16 -0
  78. data/specs_e2e/rails_7_2/.rubocop.yml +8 -0
  79. data/specs_e2e/rails_7_2/Gemfile +11 -0
  80. data/specs_e2e/{rails_4_2 → rails_7_2}/Rakefile +1 -1
  81. data/specs_e2e/rails_7_2/app/assets/stylesheets/application.css +15 -0
  82. data/specs_e2e/rails_7_2/app/controllers/application_controller.rb +4 -0
  83. data/specs_e2e/rails_7_2/app/controllers/posts_controller.rb +58 -0
  84. data/specs_e2e/rails_7_2/app/helpers/posts_helper.rb +2 -0
  85. data/specs_e2e/rails_7_2/app/jobs/application_job.rb +7 -0
  86. data/specs_e2e/rails_7_2/app/models/application_record.rb +3 -0
  87. data/specs_e2e/rails_7_2/app/models/post.rb +2 -0
  88. data/specs_e2e/rails_7_2/app/views/layouts/application.html.erb +22 -0
  89. data/specs_e2e/rails_7_2/app/views/posts/_form.html.erb +32 -0
  90. data/specs_e2e/rails_7_2/app/views/posts/edit.html.erb +6 -0
  91. data/specs_e2e/rails_7_2/app/views/posts/index.html.erb +31 -0
  92. data/specs_e2e/rails_7_2/app/views/posts/new.html.erb +5 -0
  93. data/specs_e2e/rails_7_2/app/views/posts/show.html.erb +19 -0
  94. data/specs_e2e/rails_7_2/bin/brakeman +7 -0
  95. data/specs_e2e/rails_7_2/bin/bundle +109 -0
  96. data/specs_e2e/rails_7_2/bin/importmap +4 -0
  97. data/specs_e2e/rails_7_2/bin/rails +4 -0
  98. data/specs_e2e/rails_7_2/bin/rake +4 -0
  99. data/specs_e2e/rails_7_2/bin/setup +37 -0
  100. data/specs_e2e/rails_7_2/config/application.rb +37 -0
  101. data/specs_e2e/rails_7_2/config/boot.rb +4 -0
  102. data/specs_e2e/rails_7_2/config/cable.yml +10 -0
  103. data/specs_e2e/rails_7_2/config/credentials.yml.enc +1 -0
  104. data/specs_e2e/rails_7_2/config/database.yml +32 -0
  105. data/specs_e2e/{rails_4_2 → rails_7_2}/config/environment.rb +1 -1
  106. data/specs_e2e/rails_7_2/config/environments/development.rb +54 -0
  107. data/specs_e2e/rails_7_2/config/environments/production.rb +105 -0
  108. data/specs_e2e/rails_7_2/config/environments/test.rb +45 -0
  109. data/specs_e2e/rails_7_2/config/importmap.rb +7 -0
  110. data/specs_e2e/rails_7_2/config/initializers/content_security_policy.rb +25 -0
  111. data/specs_e2e/rails_7_2/config/initializers/filter_parameter_logging.rb +8 -0
  112. data/specs_e2e/{rails_5_2 → rails_7_2}/config/initializers/inflections.rb +4 -4
  113. data/specs_e2e/rails_7_2/config/initializers/permissions_policy.rb +13 -0
  114. data/specs_e2e/rails_7_2/config/locales/en.yml +31 -0
  115. data/specs_e2e/rails_7_2/config/master.key +1 -0
  116. data/specs_e2e/rails_7_2/config/puma.rb +34 -0
  117. data/specs_e2e/rails_7_2/config/routes.rb +5 -0
  118. data/specs_e2e/rails_7_2/config/storage.yml +34 -0
  119. data/specs_e2e/{rails_4_2 → rails_7_2}/config.ru +3 -1
  120. data/specs_e2e/rails_7_2/db/migrate/20180621085832_create_posts.rb +11 -0
  121. data/specs_e2e/rails_7_2/db/seeds.rb +9 -0
  122. data/specs_e2e/rails_7_2/db/test.sqlite3-shm +0 -0
  123. data/specs_e2e/rails_7_2/db/test.sqlite3-wal +0 -0
  124. data/specs_e2e/rails_7_2/package.json +8 -0
  125. data/specs_e2e/rails_7_2/playwright-report/index.html +71 -0
  126. data/specs_e2e/{rails_4_2 → rails_7_2}/public/404.html +6 -6
  127. data/specs_e2e/rails_7_2/public/406-unsupported-browser.html +66 -0
  128. data/specs_e2e/{rails_4_2 → rails_7_2}/public/422.html +6 -6
  129. data/specs_e2e/{rails_4_2 → rails_7_2}/public/500.html +6 -6
  130. data/specs_e2e/rails_7_2/public/icon.png +0 -0
  131. data/specs_e2e/rails_7_2/public/icon.svg +3 -0
  132. data/specs_e2e/rails_7_2/public/robots.txt +1 -0
  133. data/specs_e2e/rails_7_2/storage/test.sqlite3 +0 -0
  134. data/specs_e2e/rails_7_2/test/controllers/posts_controller_test.rb +48 -0
  135. data/specs_e2e/rails_7_2/test/cypress_fixtures/posts.yml +11 -0
  136. data/specs_e2e/rails_7_2/test/fixtures/posts.yml +11 -0
  137. data/specs_e2e/rails_7_2/test/models/post_test.rb +7 -0
  138. data/specs_e2e/rails_7_2/test-results/.last-run.json +4 -0
  139. data/specs_e2e/rails_7_2/test.sh +57 -0
  140. data/specs_e2e/rails_8/.gitattributes +9 -0
  141. data/specs_e2e/rails_8/.gitignore +16 -0
  142. data/specs_e2e/rails_8/.rubocop.yml +8 -0
  143. data/specs_e2e/rails_8/Gemfile +20 -0
  144. data/specs_e2e/{rails_3_2 → rails_8}/Rakefile +2 -3
  145. data/specs_e2e/rails_8/app/assets/stylesheets/application.css +10 -0
  146. data/specs_e2e/rails_8/app/controllers/application_controller.rb +4 -0
  147. data/specs_e2e/rails_8/app/controllers/posts_controller.rb +58 -0
  148. data/specs_e2e/rails_8/app/helpers/posts_helper.rb +2 -0
  149. data/specs_e2e/rails_8/app/jobs/application_job.rb +7 -0
  150. data/specs_e2e/rails_8/app/models/application_record.rb +3 -0
  151. data/specs_e2e/rails_8/app/models/post.rb +2 -0
  152. data/specs_e2e/rails_8/app/views/layouts/application.html.erb +27 -0
  153. data/specs_e2e/rails_8/app/views/posts/_form.html.erb +32 -0
  154. data/specs_e2e/rails_8/app/views/posts/edit.html.erb +6 -0
  155. data/specs_e2e/rails_8/app/views/posts/index.html.erb +31 -0
  156. data/specs_e2e/rails_8/app/views/posts/new.html.erb +5 -0
  157. data/specs_e2e/rails_8/app/views/posts/show.html.erb +19 -0
  158. data/specs_e2e/rails_8/bin/brakeman +7 -0
  159. data/specs_e2e/rails_8/bin/bundle +109 -0
  160. data/specs_e2e/rails_8/bin/dev +2 -0
  161. data/specs_e2e/rails_8/bin/importmap +4 -0
  162. data/specs_e2e/rails_8/bin/rails +4 -0
  163. data/specs_e2e/rails_8/bin/rake +4 -0
  164. data/specs_e2e/rails_8/bin/setup +34 -0
  165. data/specs_e2e/rails_8/bin/thrust +5 -0
  166. data/specs_e2e/rails_8/config/application.rb +27 -0
  167. data/specs_e2e/rails_8/config/boot.rb +4 -0
  168. data/specs_e2e/rails_8/config/cable.yml +17 -0
  169. data/specs_e2e/rails_8/config/cache.yml +16 -0
  170. data/specs_e2e/rails_8/config/credentials.yml.enc +1 -0
  171. data/specs_e2e/rails_8/config/database.yml +41 -0
  172. data/specs_e2e/rails_8/config/deploy.yml +116 -0
  173. data/specs_e2e/rails_8/config/environment.rb +5 -0
  174. data/specs_e2e/rails_8/config/environments/development.rb +57 -0
  175. data/specs_e2e/rails_8/config/environments/production.rb +90 -0
  176. data/specs_e2e/rails_8/config/environments/test.rb +45 -0
  177. data/specs_e2e/rails_8/config/importmap.rb +7 -0
  178. data/specs_e2e/rails_8/config/initializers/content_security_policy.rb +25 -0
  179. data/specs_e2e/rails_8/config/initializers/filter_parameter_logging.rb +8 -0
  180. data/specs_e2e/rails_8/config/initializers/inflections.rb +16 -0
  181. data/specs_e2e/rails_8/config/locales/en.yml +31 -0
  182. data/specs_e2e/rails_8/config/master.key +1 -0
  183. data/specs_e2e/rails_8/config/puma.rb +41 -0
  184. data/specs_e2e/rails_8/config/queue.yml +18 -0
  185. data/specs_e2e/rails_8/config/recurring.yml +10 -0
  186. data/specs_e2e/rails_8/config/routes.rb +5 -0
  187. data/specs_e2e/rails_8/config/storage.yml +34 -0
  188. data/specs_e2e/rails_8/config.ru +6 -0
  189. data/specs_e2e/rails_8/db/cable_schema.rb +11 -0
  190. data/specs_e2e/rails_8/db/cache_schema.rb +14 -0
  191. data/specs_e2e/rails_8/db/migrate/20180621085832_create_posts.rb +11 -0
  192. data/specs_e2e/rails_8/db/queue_schema.rb +129 -0
  193. data/specs_e2e/rails_8/db/seeds.rb +9 -0
  194. data/specs_e2e/rails_8/package.json +8 -0
  195. data/specs_e2e/rails_8/playwright-report/index.html +71 -0
  196. data/specs_e2e/rails_8/public/400.html +114 -0
  197. data/specs_e2e/rails_8/public/404.html +114 -0
  198. data/specs_e2e/rails_8/public/406-unsupported-browser.html +114 -0
  199. data/specs_e2e/rails_8/public/422.html +114 -0
  200. data/specs_e2e/rails_8/public/500.html +114 -0
  201. data/specs_e2e/rails_8/public/icon.png +0 -0
  202. data/specs_e2e/rails_8/public/icon.svg +3 -0
  203. data/specs_e2e/rails_8/public/robots.txt +1 -0
  204. data/specs_e2e/rails_8/storage/test.sqlite3 +0 -0
  205. data/specs_e2e/rails_8/storage/test.sqlite3-shm +0 -0
  206. data/specs_e2e/rails_8/storage/test.sqlite3-wal +0 -0
  207. data/specs_e2e/rails_8/test/application_system_test_case.rb +5 -0
  208. data/specs_e2e/rails_8/test/controllers/posts_controller_test.rb +48 -0
  209. data/specs_e2e/rails_8/test/cypress_fixtures/posts.yml +11 -0
  210. data/specs_e2e/rails_8/test/fixtures/posts.yml +11 -0
  211. data/specs_e2e/rails_8/test/models/post_test.rb +7 -0
  212. data/specs_e2e/rails_8/test/test_helper.rb +15 -0
  213. data/specs_e2e/rails_8/test-results/.last-run.json +4 -0
  214. data/specs_e2e/{rails_5_2 → rails_8}/test.sh +1 -1
  215. metadata +246 -150
  216. data/lib/cypress_on_rails/vcr_middleware.rb +0 -73
  217. data/spec/cypress_on_rails/vcr_middleware_spec.rb +0 -119
  218. data/specs_e2e/rails_3_2/.gitignore +0 -9
  219. data/specs_e2e/rails_3_2/.ruby_version +0 -1
  220. data/specs_e2e/rails_3_2/Gemfile +0 -7
  221. data/specs_e2e/rails_3_2/README.rdoc +0 -261
  222. data/specs_e2e/rails_3_2/app/assets/stylesheets/application.css +0 -13
  223. data/specs_e2e/rails_3_2/app/controllers/application_controller.rb +0 -3
  224. data/specs_e2e/rails_3_2/app/controllers/welcome_controller.rb +0 -4
  225. data/specs_e2e/rails_3_2/app/helpers/application_helper.rb +0 -2
  226. data/specs_e2e/rails_3_2/app/models/post.rb +0 -21
  227. data/specs_e2e/rails_3_2/app/views/layouts/application.html.erb +0 -13
  228. data/specs_e2e/rails_3_2/app/views/welcome/index.html.erb +0 -24
  229. data/specs_e2e/rails_3_2/bin/rails +0 -6
  230. data/specs_e2e/rails_3_2/config/application.rb +0 -68
  231. data/specs_e2e/rails_3_2/config/boot.rb +0 -6
  232. data/specs_e2e/rails_3_2/config/environment.rb +0 -5
  233. data/specs_e2e/rails_3_2/config/environments/development.rb +0 -31
  234. data/specs_e2e/rails_3_2/config/environments/production.rb +0 -64
  235. data/specs_e2e/rails_3_2/config/environments/test.rb +0 -35
  236. data/specs_e2e/rails_3_2/config/initializers/backtrace_silencers.rb +0 -7
  237. data/specs_e2e/rails_3_2/config/initializers/inflections.rb +0 -15
  238. data/specs_e2e/rails_3_2/config/initializers/mime_types.rb +0 -5
  239. data/specs_e2e/rails_3_2/config/initializers/secret_token.rb +0 -7
  240. data/specs_e2e/rails_3_2/config/initializers/session_store.rb +0 -8
  241. data/specs_e2e/rails_3_2/config/locales/en.yml +0 -5
  242. data/specs_e2e/rails_3_2/config/routes.rb +0 -60
  243. data/specs_e2e/rails_3_2/config.ru +0 -4
  244. data/specs_e2e/rails_3_2/public/404.html +0 -26
  245. data/specs_e2e/rails_3_2/public/422.html +0 -26
  246. data/specs_e2e/rails_3_2/public/500.html +0 -25
  247. data/specs_e2e/rails_3_2/public/robots.txt +0 -5
  248. data/specs_e2e/rails_3_2/test.sh +0 -50
  249. data/specs_e2e/rails_4_2/.gitignore +0 -12
  250. data/specs_e2e/rails_4_2/Gemfile +0 -11
  251. data/specs_e2e/rails_4_2/README.rdoc +0 -28
  252. data/specs_e2e/rails_4_2/app/assets/javascripts/using_vcr.js +0 -2
  253. data/specs_e2e/rails_4_2/app/assets/stylesheets/using_vcr.css +0 -4
  254. data/specs_e2e/rails_4_2/app/controllers/application_controller.rb +0 -5
  255. data/specs_e2e/rails_4_2/app/controllers/using_vcr_controller.rb +0 -10
  256. data/specs_e2e/rails_4_2/app/controllers/welcome_controller.rb +0 -4
  257. data/specs_e2e/rails_4_2/app/models/post.rb +0 -23
  258. data/specs_e2e/rails_4_2/app/views/layouts/application.html.erb +0 -12
  259. data/specs_e2e/rails_4_2/app/views/using_vcr/index.html.erb +0 -6
  260. data/specs_e2e/rails_4_2/app/views/using_vcr/record_cats.html.erb +0 -7
  261. data/specs_e2e/rails_4_2/app/views/welcome/index.html.erb +0 -24
  262. data/specs_e2e/rails_4_2/bin/bundle +0 -3
  263. data/specs_e2e/rails_4_2/bin/rails +0 -4
  264. data/specs_e2e/rails_4_2/bin/rake +0 -4
  265. data/specs_e2e/rails_4_2/bin/setup +0 -29
  266. data/specs_e2e/rails_4_2/config/application.rb +0 -32
  267. data/specs_e2e/rails_4_2/config/boot.rb +0 -3
  268. data/specs_e2e/rails_4_2/config/environments/development.rb +0 -25
  269. data/specs_e2e/rails_4_2/config/environments/production.rb +0 -64
  270. data/specs_e2e/rails_4_2/config/environments/test.rb +0 -42
  271. data/specs_e2e/rails_4_2/config/initializers/backtrace_silencers.rb +0 -7
  272. data/specs_e2e/rails_4_2/config/initializers/cookies_serializer.rb +0 -3
  273. data/specs_e2e/rails_4_2/config/initializers/session_store.rb +0 -3
  274. data/specs_e2e/rails_4_2/config/initializers/to_time_preserves_timezone.rb +0 -10
  275. data/specs_e2e/rails_4_2/config/initializers/wrap_parameters.rb +0 -9
  276. data/specs_e2e/rails_4_2/config/locales/en.yml +0 -23
  277. data/specs_e2e/rails_4_2/config/routes.rb +0 -61
  278. data/specs_e2e/rails_4_2/config/secrets.yml +0 -22
  279. data/specs_e2e/rails_4_2/package.json +0 -12
  280. data/specs_e2e/rails_4_2/playwright-report/index.html +0 -62
  281. data/specs_e2e/rails_4_2/public/favicon.ico +0 -0
  282. data/specs_e2e/rails_4_2/public/robots.txt +0 -5
  283. data/specs_e2e/rails_4_2/spec/fixtures/vcr_cassettes/cats.yml +0 -63
  284. data/specs_e2e/rails_5_2/Gemfile +0 -15
  285. data/specs_e2e/rails_5_2/app/assets/javascripts/posts.js +0 -2
  286. data/specs_e2e/rails_5_2/app/assets/stylesheets/posts.css +0 -4
  287. data/specs_e2e/rails_5_2/app/assets/stylesheets/scaffold.css +0 -80
  288. data/specs_e2e/rails_5_2/app/jobs/application_job.rb +0 -2
  289. data/specs_e2e/rails_5_2/app/views/welcome/index.html.erb +0 -5
  290. data/specs_e2e/rails_5_2/bin/bundle +0 -3
  291. data/specs_e2e/rails_5_2/bin/rails +0 -4
  292. data/specs_e2e/rails_5_2/bin/rake +0 -4
  293. data/specs_e2e/rails_5_2/bin/update +0 -25
  294. data/specs_e2e/rails_5_2/config/application.rb +0 -33
  295. data/specs_e2e/rails_5_2/config/boot.rb +0 -4
  296. data/specs_e2e/rails_5_2/config/credentials.yml.enc +0 -1
  297. data/specs_e2e/rails_5_2/config/environments/production.rb +0 -68
  298. data/specs_e2e/rails_5_2/config/initializers/backtrace_silencers.rb +0 -7
  299. data/specs_e2e/rails_5_2/config/initializers/filter_parameter_logging.rb +0 -4
  300. data/specs_e2e/rails_5_2/config/initializers/mime_types.rb +0 -4
  301. data/specs_e2e/rails_5_2/config/initializers/wrap_parameters.rb +0 -9
  302. data/specs_e2e/rails_5_2/config/master.key +0 -1
  303. data/specs_e2e/rails_5_2/public/favicon.ico +0 -0
  304. data/specs_e2e/rails_5_2/public/robots.txt +0 -1
  305. /data/specs_e2e/{rails_5_2 → rails_6_1}/.gitignore +0 -0
  306. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/controllers/application_controller.rb +0 -0
  307. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/controllers/posts_controller.rb +0 -0
  308. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/helpers/posts_helper.rb +0 -0
  309. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/models/application_record.rb +0 -0
  310. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/models/post.rb +0 -0
  311. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/views/posts/_form.html.erb +0 -0
  312. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/views/posts/edit.html.erb +0 -0
  313. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/views/posts/index.html.erb +0 -0
  314. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/views/posts/new.html.erb +0 -0
  315. /data/specs_e2e/{rails_5_2 → rails_6_1}/app/views/posts/show.html.erb +0 -0
  316. /data/specs_e2e/{rails_5_2 → rails_6_1}/config/initializers/application_controller_renderer.rb +0 -0
  317. /data/specs_e2e/{rails_5_2 → rails_6_1}/config/initializers/cookies_serializer.rb +0 -0
  318. /data/specs_e2e/{rails_4_2 → rails_6_1}/config/initializers/inflections.rb +0 -0
  319. /data/specs_e2e/{rails_4_2 → rails_6_1}/config/initializers/mime_types.rb +0 -0
  320. /data/specs_e2e/{rails_5_2 → rails_6_1}/config/routes.rb +0 -0
  321. /data/specs_e2e/{rails_5_2 → rails_6_1}/public/404.html +0 -0
  322. /data/specs_e2e/{rails_5_2 → rails_6_1}/public/422.html +0 -0
  323. /data/specs_e2e/{rails_5_2 → rails_6_1}/public/500.html +0 -0
  324. /data/specs_e2e/{rails_5_2 → rails_6_1}/public/apple-touch-icon-precomposed.png +0 -0
  325. /data/specs_e2e/{rails_5_2 → rails_6_1}/public/apple-touch-icon.png +0 -0
  326. /data/specs_e2e/{rails_3_2 → rails_6_1}/public/favicon.ico +0 -0
  327. /data/specs_e2e/{rails_5_2 → rails_6_1}/test/controllers/posts_controller_test.rb +0 -0
  328. /data/specs_e2e/{rails_5_2 → rails_6_1}/test/cypress_fixtures/posts.yml +0 -0
  329. /data/specs_e2e/{rails_5_2 → rails_6_1}/test/fixtures/posts.yml +0 -0
  330. /data/specs_e2e/{rails_5_2 → rails_6_1}/test/models/post_test.rb +0 -0
  331. /data/specs_e2e/{rails_3_2/log → rails_6_1/vendor}/.keep +0 -0
  332. /data/specs_e2e/{rails_3_2/tmp → rails_7_2/vendor}/.keep +0 -0
  333. /data/specs_e2e/{rails_4_2/spec → rails_7_2/vendor/javascript}/.keep +0 -0
  334. /data/specs_e2e/{rails_5_2 → rails_8}/README.md +0 -0
  335. /data/specs_e2e/{rails_4_2/vendor → rails_8/storage}/.keep +0 -0
  336. /data/specs_e2e/{rails_5_2 → rails_8}/vendor/.keep +0 -0
  337. /data/specs_e2e/{rails_3_2/vendor/.gitkeep → rails_8/vendor/javascript/.keep} +0 -0
@@ -0,0 +1,678 @@
1
+ # Best Practices Guide
2
+
3
+ This guide provides recommended patterns and practices for using cypress-playwright-on-rails effectively.
4
+
5
+ ## Table of Contents
6
+ - [Project Structure](#project-structure)
7
+ - [Test Organization](#test-organization)
8
+ - [Data Management](#data-management)
9
+ - [Performance Optimization](#performance-optimization)
10
+ - [CI/CD Integration](#cicd-integration)
11
+ - [Security Considerations](#security-considerations)
12
+ - [Debugging Strategies](#debugging-strategies)
13
+ - [Common Patterns](#common-patterns)
14
+
15
+ ## Project Structure
16
+
17
+ ### Recommended Directory Layout
18
+ ```
19
+ ├── e2e/ # All E2E test related files
20
+ │ ├── cypress/ # Cypress tests
21
+ │ │ ├── e2e/ # Test specs
22
+ │ │ │ ├── auth/ # Grouped by feature
23
+ │ │ │ ├── users/
24
+ │ │ │ └── products/
25
+ │ │ ├── fixtures/ # Test data
26
+ │ │ ├── support/ # Helpers and commands
27
+ │ │ └── downloads/ # Downloaded files during tests
28
+ │ ├── playwright/ # Playwright tests
29
+ │ │ ├── e2e/ # Test specs
30
+ │ │ └── support/ # Helpers
31
+ │ ├── app_commands/ # Shared Ruby commands
32
+ │ │ ├── scenarios/ # Complex test setups
33
+ │ │ └── helpers/ # Ruby helper modules
34
+ │ └── shared/ # Shared utilities
35
+ └── config/
36
+ └── initializers/
37
+ └── cypress_on_rails.rb # Configuration
38
+ ```
39
+
40
+ ### Separating Concerns
41
+ ```ruby
42
+ # e2e/app_commands/helpers/test_data.rb
43
+ module TestData
44
+ def self.standard_user
45
+ {
46
+ name: 'John Doe',
47
+ email: 'john@example.com',
48
+ role: 'user'
49
+ }
50
+ end
51
+
52
+ def self.admin_user
53
+ {
54
+ name: 'Admin User',
55
+ email: 'admin@example.com',
56
+ role: 'admin'
57
+ }
58
+ end
59
+ end
60
+
61
+ # e2e/app_commands/scenarios/standard_setup.rb
62
+ require_relative '../helpers/test_data'
63
+
64
+ User.create!(TestData.standard_user)
65
+ User.create!(TestData.admin_user)
66
+ ```
67
+
68
+ ## Test Organization
69
+
70
+ ### Group Related Tests
71
+ ```js
72
+ // e2e/cypress/e2e/users/registration.cy.js
73
+ describe('User Registration', () => {
74
+ context('Valid Input', () => {
75
+ it('registers with email', () => {
76
+ // Test implementation
77
+ });
78
+
79
+ it('registers with social login', () => {
80
+ // Test implementation
81
+ });
82
+ });
83
+
84
+ context('Invalid Input', () => {
85
+ it('shows error for duplicate email', () => {
86
+ // Test implementation
87
+ });
88
+ });
89
+ });
90
+ ```
91
+
92
+ ### Use Page Objects
93
+ ```js
94
+ // e2e/cypress/support/pages/LoginPage.js
95
+ class LoginPage {
96
+ visit() {
97
+ cy.visit('/login');
98
+ }
99
+
100
+ fillEmail(email) {
101
+ cy.get('[data-cy=email]').type(email);
102
+ }
103
+
104
+ fillPassword(password) {
105
+ cy.get('[data-cy=password]').type(password);
106
+ }
107
+
108
+ submit() {
109
+ cy.get('[data-cy=submit]').click();
110
+ }
111
+
112
+ login(email, password) {
113
+ this.visit();
114
+ this.fillEmail(email);
115
+ this.fillPassword(password);
116
+ this.submit();
117
+ }
118
+ }
119
+
120
+ export default new LoginPage();
121
+
122
+ // Usage in test
123
+ import LoginPage from '../../support/pages/LoginPage';
124
+
125
+ it('user can login', () => {
126
+ LoginPage.login('user@example.com', 'password');
127
+ cy.url().should('include', '/dashboard');
128
+ });
129
+ ```
130
+
131
+ ### Data-Test Attributes
132
+ ```erb
133
+ <!-- app/views/users/new.html.erb -->
134
+ <form data-cy="registration-form">
135
+ <input
136
+ type="email"
137
+ name="user[email]"
138
+ data-cy="email-input"
139
+ data-test-id="user-email"
140
+ />
141
+ <button
142
+ type="submit"
143
+ data-cy="submit-button"
144
+ data-test-id="register-submit"
145
+ >
146
+ Register
147
+ </button>
148
+ </form>
149
+ ```
150
+
151
+ ```js
152
+ // Prefer data-cy or data-test-id over classes/IDs
153
+ cy.get('[data-cy=email-input]').type('test@example.com');
154
+ // NOT: cy.get('#email').type('test@example.com');
155
+ ```
156
+
157
+ ## Data Management
158
+
159
+ ### Factory Bot Best Practices
160
+ ```ruby
161
+ # spec/factories/users.rb
162
+ FactoryBot.define do
163
+ factory :user do
164
+ sequence(:email) { |n| "user#{n}@example.com" }
165
+ name { Faker::Name.name }
166
+ confirmed_at { Time.current }
167
+
168
+ trait :admin do
169
+ role { 'admin' }
170
+ end
171
+
172
+ trait :with_posts do
173
+ transient do
174
+ posts_count { 3 }
175
+ end
176
+
177
+ after(:create) do |user, evaluator|
178
+ create_list(:post, evaluator.posts_count, user: user)
179
+ end
180
+ end
181
+ end
182
+ end
183
+ ```
184
+
185
+ ### Scenario Patterns
186
+ ```ruby
187
+ # e2e/app_commands/scenarios/e_commerce_setup.rb
188
+ class ECommerceSetup
189
+ def self.run(options = {})
190
+ ActiveRecord::Base.transaction do
191
+ # Create categories
192
+ categories = create_categories
193
+
194
+ # Create products
195
+ products = create_products(categories)
196
+
197
+ # Create users
198
+ users = create_users
199
+
200
+ # Create orders
201
+ create_orders(users, products) if options[:with_orders]
202
+
203
+ { categories: categories, products: products, users: users }
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ def self.create_categories
210
+ ['Electronics', 'Clothing', 'Books'].map do |name|
211
+ Category.create!(name: name)
212
+ end
213
+ end
214
+
215
+ def self.create_products(categories)
216
+ # Implementation
217
+ end
218
+ end
219
+
220
+ # Usage in test
221
+ ECommerceSetup.run(with_orders: true)
222
+ ```
223
+
224
+ ### Database Cleaning Strategies
225
+ ```ruby
226
+ # e2e/app_commands/clean.rb
227
+ class SmartCleaner
228
+ PRESERVE_TABLES = %w[
229
+ schema_migrations
230
+ ar_internal_metadata
231
+ spatial_ref_sys # PostGIS
232
+ ].freeze
233
+
234
+ def self.clean(strategy: :transaction)
235
+ case strategy
236
+ when :transaction
237
+ DatabaseCleaner.strategy = :transaction
238
+ when :truncation
239
+ DatabaseCleaner.strategy = :truncation, {
240
+ except: PRESERVE_TABLES
241
+ }
242
+ when :deletion
243
+ DatabaseCleaner.strategy = :deletion, {
244
+ except: PRESERVE_TABLES
245
+ }
246
+ end
247
+
248
+ DatabaseCleaner.clean
249
+
250
+ # Reset sequences
251
+ reset_sequences if postgresql?
252
+
253
+ # Clear caches
254
+ clear_caches
255
+ end
256
+
257
+ private
258
+
259
+ def self.postgresql?
260
+ ActiveRecord::Base.connection.adapter_name == 'PostgreSQL'
261
+ end
262
+
263
+ def self.reset_sequences
264
+ ActiveRecord::Base.connection.tables.each do |table|
265
+ ActiveRecord::Base.connection.reset_pk_sequence!(table)
266
+ end
267
+ end
268
+
269
+ def self.clear_caches
270
+ Rails.cache.clear
271
+ I18n.reload! if defined?(I18n)
272
+ end
273
+ end
274
+ ```
275
+
276
+ ## Performance Optimization
277
+
278
+ ### Minimize Database Operations
279
+ ```js
280
+ // Bad: Multiple database operations
281
+ it('creates multiple users', () => {
282
+ cy.appFactories([['create', 'user', { name: 'User 1' }]]);
283
+ cy.appFactories([['create', 'user', { name: 'User 2' }]]);
284
+ cy.appFactories([['create', 'user', { name: 'User 3' }]]);
285
+ });
286
+
287
+ // Good: Batch operations
288
+ it('creates multiple users', () => {
289
+ cy.appFactories([
290
+ ['create', 'user', { name: 'User 1' }],
291
+ ['create', 'user', { name: 'User 2' }],
292
+ ['create', 'user', { name: 'User 3' }]
293
+ ]);
294
+ });
295
+
296
+ // Better: Use create_list
297
+ it('creates multiple users', () => {
298
+ cy.appFactories([
299
+ ['create_list', 'user', 3]
300
+ ]);
301
+ });
302
+ ```
303
+
304
+ ### Smart Waiting Strategies
305
+ ```js
306
+ // Bad: Fixed waits
307
+ cy.wait(5000);
308
+
309
+ // Good: Wait for specific conditions
310
+ cy.get('[data-cy=loading]').should('not.exist');
311
+ cy.get('[data-cy=user-list]').should('be.visible');
312
+
313
+ // Better: Wait for API calls
314
+ cy.intercept('GET', '/api/users').as('getUsers');
315
+ cy.visit('/users');
316
+ cy.wait('@getUsers');
317
+ ```
318
+
319
+ ### Parallel Testing Configuration
320
+ ```ruby
321
+ # config/database.yml
322
+ test:
323
+ <<: *default
324
+ database: myapp_test<%= ENV['TEST_ENV_NUMBER'] %>
325
+
326
+ # config/initializers/cypress_on_rails.rb
327
+ if ENV['PARALLEL_WORKERS'].present?
328
+ CypressOnRails.configure do |c|
329
+ worker_id = ENV['TEST_ENV_NUMBER'] || '1'
330
+ c.server_port = 5000 + worker_id.to_i
331
+ end
332
+ end
333
+ ```
334
+
335
+ ## CI/CD Integration
336
+
337
+ ### GitHub Actions Example
338
+ ```yaml
339
+ # .github/workflows/e2e.yml
340
+ name: E2E Tests
341
+ on: [push, pull_request]
342
+
343
+ jobs:
344
+ cypress:
345
+ runs-on: ubuntu-latest
346
+ strategy:
347
+ matrix:
348
+ browser: [chrome, firefox, edge]
349
+
350
+ services:
351
+ postgres:
352
+ image: postgres:14
353
+ env:
354
+ POSTGRES_PASSWORD: password
355
+ options: >-
356
+ --health-cmd pg_isready
357
+ --health-interval 10s
358
+ --health-timeout 5s
359
+ --health-retries 5
360
+ ports:
361
+ - 5432:5432
362
+
363
+ steps:
364
+ - uses: actions/checkout@v3
365
+
366
+ - name: Setup Ruby
367
+ uses: ruby/setup-ruby@v1
368
+ with:
369
+ bundler-cache: true
370
+
371
+ - name: Setup Node
372
+ uses: actions/setup-node@v3
373
+ with:
374
+ node-version: '18'
375
+ cache: 'yarn'
376
+
377
+ - name: Install dependencies
378
+ run: |
379
+ yarn install --frozen-lockfile
380
+ bundle install
381
+
382
+ - name: Setup database
383
+ env:
384
+ RAILS_ENV: test
385
+ DATABASE_URL: postgresql://postgres:password@localhost:5432/test
386
+ run: |
387
+ bundle exec rails db:create
388
+ bundle exec rails db:schema:load
389
+
390
+ - name: Run E2E tests
391
+ env:
392
+ RAILS_ENV: test
393
+ DATABASE_URL: postgresql://postgres:password@localhost:5432/test
394
+ run: |
395
+ bundle exec rails cypress:run
396
+
397
+ - name: Upload artifacts
398
+ uses: actions/upload-artifact@v3
399
+ if: failure()
400
+ with:
401
+ name: cypress-artifacts-${{ matrix.browser }}
402
+ path: |
403
+ e2e/cypress/screenshots
404
+ e2e/cypress/videos
405
+ ```
406
+
407
+ ### CircleCI Configuration
408
+ ```yaml
409
+ # .circleci/config.yml
410
+ version: 2.1
411
+
412
+ orbs:
413
+ cypress: cypress-io/cypress@2
414
+
415
+ jobs:
416
+ e2e-tests:
417
+ docker:
418
+ - image: cimg/ruby:3.2-browsers
419
+ - image: cimg/postgres:14.0
420
+ environment:
421
+ POSTGRES_PASSWORD: password
422
+
423
+ parallelism: 4
424
+
425
+ steps:
426
+ - checkout
427
+
428
+ - restore_cache:
429
+ keys:
430
+ - gem-cache-{{ checksum "Gemfile.lock" }}
431
+ - yarn-cache-{{ checksum "yarn.lock" }}
432
+
433
+ - run:
434
+ name: Install dependencies
435
+ command: |
436
+ bundle install --path vendor/bundle
437
+ yarn install --frozen-lockfile
438
+
439
+ - save_cache:
440
+ key: gem-cache-{{ checksum "Gemfile.lock" }}
441
+ paths:
442
+ - vendor/bundle
443
+
444
+ - save_cache:
445
+ key: yarn-cache-{{ checksum "yarn.lock" }}
446
+ paths:
447
+ - node_modules
448
+
449
+ - run:
450
+ name: Setup database
451
+ command: |
452
+ bundle exec rails db:create db:schema:load
453
+ environment:
454
+ RAILS_ENV: test
455
+
456
+ - run:
457
+ name: Run E2E tests
458
+ command: |
459
+ TESTFILES=$(circleci tests glob "e2e/cypress/e2e/**/*.cy.js" | circleci tests split)
460
+ bundle exec rails cypress:run -- --spec $TESTFILES
461
+
462
+ - store_test_results:
463
+ path: test-results
464
+
465
+ - store_artifacts:
466
+ path: e2e/cypress/screenshots
467
+
468
+ - store_artifacts:
469
+ path: e2e/cypress/videos
470
+
471
+ workflows:
472
+ test:
473
+ jobs:
474
+ - e2e-tests
475
+ ```
476
+
477
+ ## Security Considerations
478
+
479
+ ### Protecting Test Endpoints
480
+ ```ruby
481
+ # config/initializers/cypress_on_rails.rb
482
+ CypressOnRails.configure do |c|
483
+ # Only enable in test/development
484
+ c.use_middleware = !Rails.env.production?
485
+
486
+ # Add authentication
487
+ c.before_request = lambda { |request|
488
+ # IP whitelist for CI
489
+ allowed_ips = ['127.0.0.1', '::1']
490
+ allowed_ips += ENV['ALLOWED_CI_IPS'].split(',') if ENV['ALLOWED_CI_IPS']
491
+
492
+ unless allowed_ips.include?(request.ip)
493
+ return [403, {}, ['Forbidden']]
494
+ end
495
+
496
+ # Token authentication
497
+ body = JSON.parse(request.body.string)
498
+ expected_token = ENV.fetch('CYPRESS_SECRET_TOKEN', 'development-token')
499
+
500
+ if body['auth_token'] != expected_token
501
+ return [401, {}, ['Unauthorized']]
502
+ end
503
+
504
+ nil
505
+ }
506
+ end
507
+ ```
508
+
509
+ ### Environment Variables
510
+ ```bash
511
+ # .env.test
512
+ CYPRESS_SECRET_TOKEN=secure-random-token-here
513
+ CYPRESS_BASE_URL=http://localhost:5017
514
+ CYPRESS_RAILS_HOST=localhost
515
+ CYPRESS_RAILS_PORT=5017
516
+
517
+ # Never commit real credentials
518
+ DATABASE_URL=postgresql://localhost/myapp_test
519
+ REDIS_URL=redis://localhost:6379/1
520
+ ```
521
+
522
+ ### Sanitizing Test Data
523
+ ```ruby
524
+ # e2e/app_commands/factory_bot.rb
525
+ class SafeFactoryBot
526
+ BLOCKED_FACTORIES = %w[admin super_admin payment_method].freeze
527
+
528
+ def self.create(factory_name, *args)
529
+ raise "Factory '#{factory_name}' is blocked in tests" if BLOCKED_FACTORIES.include?(factory_name.to_s)
530
+
531
+ FactoryBot.create(factory_name, *args)
532
+ end
533
+ end
534
+ ```
535
+
536
+ ## Debugging Strategies
537
+
538
+ ### Verbose Logging
539
+ ```ruby
540
+ # config/initializers/cypress_on_rails.rb
541
+ CypressOnRails.configure do |c|
542
+ c.logger = Logger.new(STDOUT)
543
+ c.logger.level = ENV['DEBUG'] ? Logger::DEBUG : Logger::INFO
544
+ end
545
+
546
+ # e2e/app_commands/debug.rb
547
+ def perform
548
+ logger.debug "Current user count: #{User.count}"
549
+ logger.debug "Environment: #{Rails.env}"
550
+ logger.debug "Database: #{ActiveRecord::Base.connection.current_database}"
551
+
552
+ result = yield if block_given?
553
+
554
+ logger.debug "Result: #{result.inspect}"
555
+ result
556
+ end
557
+ ```
558
+
559
+ ### Screenshot on Failure
560
+ ```js
561
+ // cypress/support/index.js
562
+ Cypress.on('fail', (error, runnable) => {
563
+ // Take screenshot before failing
564
+ cy.screenshot(`failed-${runnable.title}`, { capture: 'fullPage' });
565
+
566
+ // Log additional debugging info
567
+ cy.task('log', {
568
+ test: runnable.title,
569
+ error: error.message,
570
+ url: cy.url(),
571
+ timestamp: new Date().toISOString()
572
+ });
573
+
574
+ throw error;
575
+ });
576
+ ```
577
+
578
+ ### Interactive Debugging
579
+ ```js
580
+ // Add debugger statements
581
+ it('debug this test', () => {
582
+ cy.appFactories([['create', 'user']]);
583
+
584
+ cy.visit('/users');
585
+ debugger; // Cypress will pause here in open mode
586
+
587
+ cy.get('[data-cy=user-list]').should('exist');
588
+ });
589
+
590
+ // Or use cy.pause()
591
+ it('pause execution', () => {
592
+ cy.visit('/users');
593
+ cy.pause(); // Manually resume in Cypress UI
594
+ cy.get('[data-cy=user-list]').should('exist');
595
+ });
596
+ ```
597
+
598
+ ## Common Patterns
599
+
600
+ ### Authentication Flow
601
+ ```js
602
+ // cypress/support/commands.js
603
+ Cypress.Commands.add('login', (email, password) => {
604
+ cy.session([email, password], () => {
605
+ cy.visit('/login');
606
+ cy.get('[data-cy=email]').type(email);
607
+ cy.get('[data-cy=password]').type(password);
608
+ cy.get('[data-cy=submit]').click();
609
+ cy.url().should('include', '/dashboard');
610
+ });
611
+ });
612
+
613
+ // Usage
614
+ beforeEach(() => {
615
+ cy.login('user@example.com', 'password');
616
+ });
617
+ ```
618
+
619
+ ### File Upload Testing
620
+ ```js
621
+ it('uploads a file', () => {
622
+ cy.fixture('document.pdf', 'base64').then(fileContent => {
623
+ cy.get('[data-cy=file-input]').attachFile({
624
+ fileContent,
625
+ fileName: 'document.pdf',
626
+ mimeType: 'application/pdf',
627
+ encoding: 'base64'
628
+ });
629
+ });
630
+
631
+ cy.get('[data-cy=upload-button]').click();
632
+ cy.contains('File uploaded successfully');
633
+ });
634
+ ```
635
+
636
+ ### API Mocking
637
+ ```js
638
+ it('handles API errors gracefully', () => {
639
+ // Mock failed API response
640
+ cy.intercept('POST', '/api/users', {
641
+ statusCode: 500,
642
+ body: { error: 'Internal Server Error' }
643
+ }).as('createUser');
644
+
645
+ cy.visit('/users/new');
646
+ cy.get('[data-cy=submit]').click();
647
+
648
+ cy.wait('@createUser');
649
+ cy.contains('Something went wrong');
650
+ });
651
+ ```
652
+
653
+ ### Testing Async Operations
654
+ ```js
655
+ it('waits for async operations', () => {
656
+ // Start a background job
657
+ cy.appEval(`
658
+ ImportJob.perform_later('large_dataset.csv')
659
+ `);
660
+
661
+ cy.visit('/imports');
662
+
663
+ // Poll for completion
664
+ cy.get('[data-cy=import-status]', { timeout: 30000 })
665
+ .should('contain', 'Completed');
666
+ });
667
+ ```
668
+
669
+ ## Summary
670
+
671
+ Following these best practices will help you:
672
+ - Write more maintainable and reliable tests
673
+ - Improve test performance and reduce flakiness
674
+ - Better organize your test code
675
+ - Secure your test infrastructure
676
+ - Debug issues more effectively
677
+
678
+ Remember: E2E tests should focus on critical user journeys. Use unit and integration tests for comprehensive coverage of edge cases.