i18n-timeout.cy.ts 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import { mockI18nTimeout, mockAllRequiredAPIs } from '../../support/mock/handlers/i18n';
  2. import LoginPage from '../../support/pageObjects/LoginPage';
  3. describe('多语言资源网络超时测试', () => {
  4. const loginPage = new LoginPage();
  5. beforeEach(() => {
  6. cy.clearAllSessionStorage();
  7. cy.clearAllLocalStorage();
  8. // Mock所有必要的API,避免影响页面加载
  9. mockAllRequiredAPIs();
  10. });
  11. it('API请求超时时正确处理', () => {
  12. mockI18nTimeout('zh');
  13. cy.window().then((win) => {
  14. Object.defineProperty(win.navigator, 'language', {
  15. value: 'zh-CN',
  16. writable: false
  17. });
  18. });
  19. loginPage.visit();
  20. // 验证加载状态显示
  21. cy.contains('加载多语言资源中...').should('be.visible');
  22. // 等待超时请求(这里我们不会真的等30秒,而是验证请求被发起)
  23. cy.wait('@getI18nZHTimeout', { timeout: 5000 }).then((interception) => {
  24. // 验证请求确实被发起
  25. expect(interception.request.url).to.include('/dr/api/v1/pub/trans/zh/zh.js');
  26. });
  27. // 由于实际测试中不会等待30秒,我们模拟超时后的状态
  28. // 在真实场景中,这会触发错误处理
  29. });
  30. it('模拟真实超时场景的错误处理', () => {
  31. // 设置一个较短的延迟来模拟超时,然后返回错误
  32. cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
  33. // 模拟网络超时错误
  34. req.reply({
  35. statusCode: 408, // Request Timeout
  36. body: {
  37. message: 'Request Timeout',
  38. error: 'The request timed out'
  39. }
  40. });
  41. }).as('getI18nENTimeoutError');
  42. cy.window().then((win) => {
  43. Object.defineProperty(win.navigator, 'language', {
  44. value: 'en-US',
  45. writable: false
  46. });
  47. });
  48. loginPage.visit();
  49. cy.wait('@getI18nENTimeoutError');
  50. // 验证超时错误的处理
  51. cy.contains('多语言资源加载失败').should('be.visible');
  52. cy.contains('重新加载').should('be.visible');
  53. // 验证Redux状态反映超时错误
  54. cy.window().its('store').invoke('getState').then((state) => {
  55. expect(state.i18n.loading).to.be.false;
  56. expect(state.i18n.error).to.not.be.null;
  57. expect(state.i18n.messages).to.deep.equal({});
  58. });
  59. });
  60. it('验证超时后重新加载的成功恢复', () => {
  61. // 首先模拟超时错误
  62. cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
  63. req.reply({
  64. statusCode: 408,
  65. body: { message: 'Request Timeout' }
  66. });
  67. }).as('getI18nZHTimeoutFirst');
  68. cy.window().then((win) => {
  69. Object.defineProperty(win.navigator, 'language', {
  70. value: 'zh-CN',
  71. writable: false
  72. });
  73. });
  74. loginPage.visit();
  75. cy.wait('@getI18nZHTimeoutFirst');
  76. // 验证超时错误显示
  77. cy.contains('多语言资源加载失败').should('be.visible');
  78. cy.contains('重新加载').should('be.visible');
  79. // 设置成功的mock用于重新加载
  80. cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
  81. req.reply({
  82. statusCode: 200,
  83. body: {
  84. greeting: '你好,世界!',
  85. patient: '患者管理',
  86. register: '注册'
  87. }
  88. });
  89. }).as('getI18nZHSuccess');
  90. // 点击重新加载
  91. cy.contains('重新加载').click();
  92. cy.wait('@getI18nZHSuccess');
  93. // 验证成功恢复
  94. cy.contains('多语言资源加载失败').should('not.exist');
  95. cy.get('body').should('contain', '患者管理');
  96. // 验证Redux状态恢复正常
  97. cy.window().its('store').invoke('getState').then((state) => {
  98. expect(state.i18n.loading).to.be.false;
  99. expect(state.i18n.error).to.be.null;
  100. expect(state.i18n.currentLocale).to.equal('zh');
  101. expect(state.i18n.messages).to.have.property('patient', '患者管理');
  102. });
  103. });
  104. it('验证慢网络环境下的用户体验', () => {
  105. // 模拟慢网络,但不超时
  106. cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
  107. req.reply({
  108. delay: 3000, // 3秒延迟,模拟慢网络
  109. statusCode: 200,
  110. body: {
  111. greeting: 'Hello, world!',
  112. patient: 'Patient Management'
  113. }
  114. });
  115. }).as('getI18nENSlow');
  116. cy.window().then((win) => {
  117. Object.defineProperty(win.navigator, 'language', {
  118. value: 'en-US',
  119. writable: false
  120. });
  121. });
  122. loginPage.visit();
  123. // 验证加载状态持续显示
  124. cy.contains('加载多语言资源中...').should('be.visible');
  125. // 验证加载状态的样式
  126. cy.get('div').contains('加载多语言资源中...').should('be.visible')
  127. .parent().should('have.css', 'display', 'flex')
  128. .and('have.css', 'justify-content', 'center')
  129. .and('have.css', 'align-items', 'center');
  130. // 等待慢网络请求完成
  131. cy.wait('@getI18nENSlow');
  132. // 验证最终成功加载
  133. cy.contains('加载多语言资源中...').should('not.exist');
  134. cy.get('body').should('contain', 'Patient Management');
  135. });
  136. it('验证多次超时重试的行为', () => {
  137. let retryCount = 0;
  138. // 设置动态mock,前两次超时,第三次成功
  139. cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
  140. retryCount++;
  141. if (retryCount <= 2) {
  142. req.reply({
  143. statusCode: 408,
  144. body: { message: 'Request Timeout' }
  145. });
  146. } else {
  147. req.reply({
  148. statusCode: 200,
  149. body: {
  150. greeting: '你好,世界!',
  151. patient: '患者管理'
  152. }
  153. });
  154. }
  155. }).as('getI18nZHRetry');
  156. cy.window().then((win) => {
  157. Object.defineProperty(win.navigator, 'language', {
  158. value: 'zh-CN',
  159. writable: false
  160. });
  161. });
  162. loginPage.visit();
  163. // 第一次超时
  164. cy.wait('@getI18nZHRetry');
  165. cy.contains('多语言资源加载失败').should('be.visible');
  166. // 第一次重试,仍然超时
  167. cy.contains('重新加载').click();
  168. cy.wait('@getI18nZHRetry');
  169. cy.contains('多语言资源加载失败').should('be.visible');
  170. // 第二次重试,成功
  171. cy.contains('重新加载').click();
  172. cy.wait('@getI18nZHRetry');
  173. // 验证最终成功
  174. cy.contains('多语言资源加载失败').should('not.exist');
  175. cy.get('body').should('contain', '患者管理');
  176. });
  177. it('验证超时错误的详细信息显示', () => {
  178. cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
  179. req.reply({
  180. statusCode: 408,
  181. body: {
  182. message: 'Request Timeout',
  183. error: 'The server did not respond within the expected time',
  184. code: 'TIMEOUT_ERROR'
  185. }
  186. });
  187. }).as('getI18nENTimeoutDetail');
  188. cy.window().then((win) => {
  189. Object.defineProperty(win.navigator, 'language', {
  190. value: 'en-US',
  191. writable: false
  192. });
  193. });
  194. loginPage.visit();
  195. cy.wait('@getI18nENTimeoutDetail');
  196. // 验证错误信息显示
  197. cy.contains('多语言资源加载失败').should('be.visible');
  198. // 验证Redux状态包含详细错误信息
  199. cy.window().its('store').invoke('getState').then((state) => {
  200. expect(state.i18n.loading).to.be.false;
  201. expect(state.i18n.error).to.not.be.null;
  202. expect(state.i18n.error).to.include('Failed to load i18n messages');
  203. });
  204. });
  205. it('验证超时场景下其他功能不受影响', () => {
  206. cy.intercept('GET', '/dr/api/v1/pub/trans/zh/zh.js', (req) => {
  207. req.reply({
  208. statusCode: 408,
  209. body: { message: 'Request Timeout' }
  210. });
  211. }).as('getI18nZHTimeoutIsolated');
  212. cy.window().then((win) => {
  213. Object.defineProperty(win.navigator, 'language', {
  214. value: 'zh-CN',
  215. writable: false
  216. });
  217. });
  218. loginPage.visit();
  219. cy.wait('@getI18nZHTimeoutIsolated');
  220. // 验证超时错误显示
  221. cy.contains('多语言资源加载失败').should('be.visible');
  222. // 验证页面基本结构仍然存在
  223. cy.get('body').should('exist');
  224. cy.get('div').should('exist');
  225. // 验证Redux store的其他状态不受影响
  226. cy.window().its('store').invoke('getState').then((state) => {
  227. // 验证其他slice的状态正常
  228. expect(state.product).to.exist;
  229. expect(state.userInfo).to.exist;
  230. // 只有i18n状态反映超时错误
  231. expect(state.i18n.loading).to.be.false;
  232. expect(state.i18n.error).to.not.be.null;
  233. expect(state.i18n.messages).to.deep.equal({});
  234. });
  235. });
  236. it('验证网络恢复后的自动重试机制', () => {
  237. // 注意:这个测试模拟的是用户手动重试,而不是自动重试
  238. // 因为我们的实现中没有自动重试机制
  239. cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
  240. req.reply({
  241. statusCode: 408,
  242. body: { message: 'Request Timeout' }
  243. });
  244. }).as('getI18nENNetworkDown');
  245. cy.window().then((win) => {
  246. Object.defineProperty(win.navigator, 'language', {
  247. value: 'en-US',
  248. writable: false
  249. });
  250. });
  251. loginPage.visit();
  252. cy.wait('@getI18nENNetworkDown');
  253. // 验证网络问题时的错误显示
  254. cy.contains('多语言资源加载失败').should('be.visible');
  255. cy.contains('重新加载').should('be.visible');
  256. // 模拟网络恢复
  257. cy.intercept('GET', '/dr/api/v1/pub/trans/en/en.js', (req) => {
  258. req.reply({
  259. statusCode: 200,
  260. body: {
  261. greeting: 'Hello, world!',
  262. patient: 'Patient Management'
  263. }
  264. });
  265. }).as('getI18nENNetworkUp');
  266. // 用户手动重试
  267. cy.contains('重新加载').click();
  268. cy.wait('@getI18nENNetworkUp');
  269. // 验证网络恢复后成功加载
  270. cy.contains('多语言资源加载失败').should('not.exist');
  271. cy.get('body').should('contain', 'Patient Management');
  272. });
  273. });