diff --git a/cypress.config.js b/cypress.config.js index d938268..dd16b64 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -10,7 +10,5 @@ module.exports = defineConfig({ specPattern: "cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}", }, includeShadowDom: true, - env: { - // Removed hardcoded password — use cypress.env.json for real credentials - } + env: {} }); diff --git a/cypress/e2e/accountAdmin/accountDuplicationCheck.cy.js b/cypress/e2e/accountAdmin/accountDuplicationCheck.cy.js index e1552bb..43717af 100644 --- a/cypress/e2e/accountAdmin/accountDuplicationCheck.cy.js +++ b/cypress/e2e/accountAdmin/accountDuplicationCheck.cy.js @@ -1,71 +1,52 @@ +import { loginWithFixtures } from '../../support/intercept'; + const MSG_ACCOUNT_NOT_UNIQUE = 'Account has already been registered.'; -describe('Account duplication check.', ()=>{ - beforeEach(() => { - cy.visit('/account-admin'); - const username = Cypress.env('user').username; - const password = Cypress.env('user').password; - - cy.visit('/account-admin'); - - cy.get('input[id="account"]').type(username); - cy.get('input[id="password"]').type(password); - cy.get('button[type="submit"]').click(); - }); +describe('Account duplication check.', () => { + beforeEach(() => { + loginWithFixtures(); + cy.visit('/account-admin'); + cy.wait('@getUsers'); + }); - it('When an account is already existed, show the error message when the confirm button is clicked.', () => { - const testAccountName = '000000'; - cy.contains('button', 'Create New').should('be.visible'); - cy.contains('button', 'Create New').click(); - - // 在 id 為 input_account_field 的 input 元素內填入值 - cy.get('#input_account_field').type(testAccountName); - cy.get('#input_name_field').type(testAccountName); - cy.get('#input_first_pwd').type(testAccountName); - cy.get('#input_second_pwd').type(testAccountName); - cy.get('.checkbox-and-text').first().find('div').first().click(); - // 確保 Confirm 按鈕存在並可點擊 - cy.contains('button', 'Confirm') - .should('be.visible') - .and('be.enabled') - .click(); - cy.contains('Account added').should('be.visible'); //表示帳號創建成功 + it('When an account already exists, show error message on confirm.', () => { + const testAccountName = '000000'; - cy.contains('span', 'Account').should('be.visible'); // 表示畫面轉導向成功,出現清單列表 - // 接著,點選排序按鈕,排序將會造成生升冪排序,我們希望 000000 帳號排在第一個 - // 找到包含 'Account' 的 span 元素 - cy.get('span:contains("Account")') - .closest('th') - .find('span') - .not(':contains("Account")') - .first() - .click(); // 點選下一个兄弟 span 元素 - - // 等待,以確保頁面已完全渲染 - cy.wait(1000); + // First creation: account doesn't exist yet + cy.intercept('GET', '/api/users/000000', { + statusCode: 404, + body: { detail: 'Not found' }, + }).as('checkNewUser'); - // 確認 000000 帳號是否出現在第一列 - cy.get('tr').filter((index, tr) => { - // 遍歷所有的 tr 元素 - const td = Cypress.$(tr).find('td').eq(0); // 找到第一個 td 元素 - return td.text().trim() === '000000'; // 檢查其文本內容是否為 000000 - }).first().should('exist'); // 確認至少有一個符合條件的 tr 存在 + cy.contains('button', 'Create New').should('be.visible').click(); + cy.get('#input_account_field').type(testAccountName); + cy.get('#input_name_field').type(testAccountName); + cy.get('#input_first_pwd').type(testAccountName); + cy.get('.checkbox-and-text').first().find('div').first().click(); - // 接著又再次創建一個同樣名稱的帳號,企圖看是否出現錯誤訊息 - cy.contains('button', 'Create New').should('be.visible'); - cy.contains('button', 'Create New').click(); - - // 在 id 為 input_account_field 的 input 元素內填入值 - cy.get('#input_account_field').type(testAccountName); - cy.get('#input_name_field').type(testAccountName); - cy.get('#input_first_pwd').type(testAccountName); - cy.get('#input_second_pwd').type(testAccountName); - cy.get('.checkbox-and-text').first().find('div').first().click(); - // 確保 Confirm 按鈕存在並可點擊 - cy.contains('button', 'Confirm') - .should('be.visible') - .and('be.enabled') - .click(); - cy.contains('span', MSG_ACCOUNT_NOT_UNIQUE).should('be.visible'); - }); + cy.contains('button', 'Confirm') + .should('be.visible') + .and('be.enabled') + .click(); + cy.wait('@postUser'); + cy.contains('Account added').should('be.visible'); + + // Second creation: now account exists — override to return 200 + cy.intercept('GET', '/api/users/000000', { + statusCode: 200, + body: { username: '000000', name: '000000', is_admin: false, is_active: true, roles: [] }, + }).as('checkExistingUser'); + + cy.contains('button', 'Create New').should('be.visible').click(); + cy.get('#input_account_field').type(testAccountName); + cy.get('#input_name_field').type(testAccountName); + cy.get('#input_first_pwd').type(testAccountName); + cy.get('.checkbox-and-text').first().find('div').first().click(); + + cy.contains('button', 'Confirm') + .should('be.visible') + .and('be.enabled') + .click(); + cy.contains(MSG_ACCOUNT_NOT_UNIQUE).should('be.visible'); + }); }); diff --git a/cypress/e2e/accountAdmin/confirmPasswordMessage.cy.js b/cypress/e2e/accountAdmin/confirmPasswordMessage.cy.js index bfd9a0c..be1231d 100644 --- a/cypress/e2e/accountAdmin/confirmPasswordMessage.cy.js +++ b/cypress/e2e/accountAdmin/confirmPasswordMessage.cy.js @@ -1,44 +1,30 @@ -import { getRandomInt } from '../../../src/utils/jsUtils'; -const MSG_PWD_NOT_MATCHED = 'Confirm Password does not match.'; +import { loginWithFixtures } from '../../support/intercept'; -describe('Confirm that two input passwords are equal.', ()=>{ - beforeEach(() => { - cy.visit('/account-admin'); - const username = Cypress.env('user').username; - const password = Cypress.env('user').password; - - cy.visit('/account-admin'); - - cy.get('input[id="account"]').type(username); - cy.get('input[id="password"]').type(password); - cy.get('button[type="submit"]').click(); - }); +describe('Password validation on create account.', () => { + beforeEach(() => { + loginWithFixtures(); + cy.visit('/account-admin'); + cy.wait('@getUsers'); + }); - it('Confirm that when creating an account, the two input password fields are equal; otherwise, show the message.', () => { - cy.contains('button', 'Create New').should('be.visible'); - cy.contains('button', 'Create New').click(); - - const randomNumber = getRandomInt(1000); - - // 將整數轉換為四位數字串,並補零 - const fourDigitString = randomNumber.toString().padStart(4, '0'); + it('When password is too short, confirm button stays disabled.', () => { + cy.contains('button', 'Create New').should('be.visible').click(); - // 將 'unit-test-' 和生成的四位數字串組合 - const inputValue = `unit-test-${fourDigitString}`; + cy.get('#input_account_field').type('unit-test-0001'); + cy.get('#input_name_field').type('unit-test-0001'); + // Password shorter than 6 characters + cy.get('#input_first_pwd').type('aaa'); - // 在 id 為 input_account_field 的 input 元素內填入值 - cy.get('#input_account_field').type(inputValue); - cy.get('#input_name_field').type(inputValue); - cy.get('#input_first_pwd').type('aaaaaa'); - cy.get('#input_second_pwd').type('bbbbbb'); + cy.contains('button', 'Confirm').should('be.disabled'); + }); - // 確保 Confirm 按鈕存在並可點擊 - cy.contains('button', 'Confirm') - .should('be.visible') - .and('be.enabled') - .click(); + it('When password meets minimum length, confirm button enables.', () => { + cy.contains('button', 'Create New').should('be.visible').click(); - cy.contains(MSG_PWD_NOT_MATCHED).should('be.visible'); - }); + cy.get('#input_account_field').type('unit-test-0001'); + cy.get('#input_name_field').type('unit-test-0001'); + cy.get('#input_first_pwd').type('aaaaaa'); + + cy.contains('button', 'Confirm').should('be.enabled'); + }); }); - \ No newline at end of file diff --git a/cypress/e2e/accountAdmin/createAccont.cy.js b/cypress/e2e/accountAdmin/createAccont.cy.js index 8900292..75e9def 100644 --- a/cypress/e2e/accountAdmin/createAccont.cy.js +++ b/cypress/e2e/accountAdmin/createAccont.cy.js @@ -1,42 +1,36 @@ -import { getRandomInt } from '../../../src/utils/jsUtils'; +import { loginWithFixtures } from '../../support/intercept'; -describe('Create an Account', ()=>{ - beforeEach(() => { - cy.visit('/account-admin'); - const username = Cypress.env('user').username; - const password = Cypress.env('user').password; - - cy.visit('/account-admin'); - - cy.get('input[id="account"]').type(username); - cy.get('input[id="password"]').type(password); - cy.get('button[type="submit"]').click(); - }); +describe('Create an Account', () => { + beforeEach(() => { + loginWithFixtures(); + // Override: new usernames should return 404 (account doesn't exist yet) + cy.intercept('GET', '/api/users/unit-test-*', { + statusCode: 404, + body: { detail: 'Not found' }, + }).as('checkNewUser'); + cy.visit('/account-admin'); + cy.wait('@getUsers'); + }); - it('Create a new account; role is admin; should appear Saved message', () => { - cy.contains('button', 'Create New').should('be.visible'); - cy.contains('button', 'Create New').click(); - - const randomNumber = getRandomInt(1000); - - // 將整數轉換為四位數字串,並補零 - const fourDigitString = randomNumber.toString().padStart(4, '0'); + it('Create a new account with admin role; should show saved message.', () => { + cy.contains('button', 'Create New').should('be.visible').click(); - // 將 'unit-test-' 和生成的四位數字串組合 - const inputValue = `unit-test-${fourDigitString}`; + cy.get('#input_account_field').type('unit-test-0001'); + cy.get('#input_name_field').type('unit-test-0001'); + cy.get('#input_first_pwd').type('aaaaaa'); + cy.get('.checkbox-and-text').first().find('div').first().click(); - // 在 id 為 input_account_field 的 input 元素內填入值 - cy.get('#input_account_field').type(inputValue); - cy.get('#input_name_field').type(inputValue); - cy.get('#input_first_pwd').type('aaaaaa'); - cy.get('#input_second_pwd').type('aaaaaa'); - cy.get('.checkbox-and-text').first().find('div').first().click(); - // 確保 Confirm 按鈕存在並可點擊 - cy.contains('button', 'Confirm') - .should('be.visible') - .and('be.enabled') - .click(); - cy.contains('Account added').should('be.visible'); //表示帳號創建成功 - }); + cy.contains('button', 'Confirm') + .should('be.visible') + .and('be.enabled') + .click(); + cy.wait('@postUser'); + cy.contains('Account added').should('be.visible'); + }); + + it('Confirm button is disabled when required fields are empty.', () => { + cy.contains('button', 'Create New').should('be.visible').click(); + cy.get('#input_account_field').type('test'); + cy.contains('button', 'Confirm').should('be.disabled'); + }); }); - \ No newline at end of file diff --git a/cypress/e2e/accountAdmin/deleteAccount.cy.js b/cypress/e2e/accountAdmin/deleteAccount.cy.js index fa76067..b37b1ee 100644 --- a/cypress/e2e/accountAdmin/deleteAccount.cy.js +++ b/cypress/e2e/accountAdmin/deleteAccount.cy.js @@ -1,57 +1,24 @@ -describe('Delete an Account', ()=>{ - beforeEach(() => { - cy.visit('/account-admin'); - const username = Cypress.env('user').username; - const password = Cypress.env('user').password; - - cy.visit('/account-admin'); - - cy.get('input[id="account"]').type(username); - cy.get('input[id="password"]').type(password); - cy.get('button[type="submit"]').click(); - }); +import { loginWithFixtures } from '../../support/intercept'; - it('Delete an account just created, which is named 000000.', () => { - cy.contains('button', 'Create New').should('be.visible'); - cy.contains('button', 'Create New').click(); - - // 在 id 為 input_account_field 的 input 元素內填入值 - cy.get('#input_account_field').type('000000'); - cy.get('#input_name_field').type('000000'); - cy.get('#input_first_pwd').type('aaaaaa'); - cy.get('#input_second_pwd').type('aaaaaa'); - cy.get('.checkbox-and-text').first().find('div').first().click(); - // 確保 Confirm 按鈕存在並可點擊 - cy.contains('button', 'Confirm') - .should('be.visible') - .and('be.enabled') - .click(); - cy.contains('Account added').should('be.visible'); //表示帳號創建成功 +describe('Delete an Account', () => { + beforeEach(() => { + loginWithFixtures(); + cy.visit('/account-admin'); + cy.wait('@getUsers'); + }); - cy.contains('span', 'Account').should('be.visible'); // 表示畫面轉導向成功,出現清單列表 - // 接著,點選排序按鈕,排序將會造成生升冪排序,我們希望 000000 帳號排在第一個 - // 找到包含 'Account' 的 span 元素 - cy.get('span:contains("Account")') - .closest('th') - .find('span') - .not(':contains("Account")') - .first() - .click(); // 點選下一个兄弟 span 元素 - - // 等待,以確保頁面已完全渲染 - cy.wait(1000); + it('Delete button opens confirmation modal and deletes on confirm.', () => { + cy.get('img.delete-account').first().click(); + cy.contains('ARE YOU SURE TO DELETE').should('be.visible'); + cy.get('#sure_to_delete_acct_btn').click(); + cy.wait('@deleteUser'); + cy.contains('Account deleted').should('be.visible'); + }); - // 確認 000000 帳號是否出現在第一列 - cy.get('tr').filter((index, tr) => { - // 遍歷所有的 tr 元素 - const td = Cypress.$(tr).find('td').eq(0); // 找到第一個 td 元素 - return td.text().trim() === '000000'; // 檢查其文本內容是否為 000000 - }).first().should('exist'); // 確認至少有一個符合條件的 tr 存在 - - cy.get('img.delete-account').first().click(); - cy.contains('h1', 'ARE YOU SURE TO DELETE ?').should('be.visible'); - cy.contains('button', 'Yes').click(); - - cy.contains('Account deleted').should('be.visible'); //表示帳號刪除成功 - }); + it('Cancel button closes the delete confirmation modal.', () => { + cy.get('img.delete-account').first().click(); + cy.contains('ARE YOU SURE TO DELETE').should('be.visible'); + cy.get('#calcel_delete_acct_btn').click(); + cy.contains('ARE YOU SURE TO DELETE').should('not.exist'); + }); }); diff --git a/cypress/e2e/accountAdmin/editAccount.cy.js b/cypress/e2e/accountAdmin/editAccount.cy.js index 571e5ca..891945f 100644 --- a/cypress/e2e/accountAdmin/editAccount.cy.js +++ b/cypress/e2e/accountAdmin/editAccount.cy.js @@ -1,61 +1,27 @@ -import { getRandomInt } from '../../../src/utils/jsUtils'; +import { loginWithFixtures } from '../../support/intercept'; -const TEST_ACCOUNT = '000000'; const MODAL_TITLE_ACCOUNT_EDIT = 'Account Edit'; const MSG_ACCOUNT_EDITED = 'Saved'; -const NUM_OF_SCROLLS = 5; -Cypress.Commands.add('manualScrollToBottom', (repeats = 20) => { - for (let i = 0; i < repeats; i++) { - cy.scrollTo('bottom', { duration: 500 }); - cy.wait(500); - } +describe('Edit an account', () => { + beforeEach(() => { + loginWithFixtures(); + cy.visit('/account-admin'); + cy.wait('@getUsers'); + }); + + it('Edit an account; modify name and see saved message.', () => { + cy.get('.btn-edit').first().click(); + cy.wait('@getUserDetail'); + + cy.contains('h1', MODAL_TITLE_ACCOUNT_EDIT).should('exist'); + cy.get('#input_name_field').clear().type('Updated Name'); + + cy.contains('button', 'Confirm') + .should('be.visible') + .and('be.enabled') + .click(); + cy.wait('@putUser'); + cy.contains(MSG_ACCOUNT_EDITED).should('be.visible'); + }); }); - -describe('Edit an account', ()=>{ - beforeEach(() => { - cy.visit('/account-admin'); - const username = Cypress.env('user').username; - const password = Cypress.env('user').password; - - cy.visit('/account-admin'); - - cy.get('input[id="account"]').type(username); - cy.get('input[id="password"]').type(password); - cy.get('button[type="submit"]').click(); - }); - - it('Edit an account; modify name and see saved message.', () => { - // 為了讓帳號出現,必須不斷往下滾動捲軸 - for(let i = 0; i < NUM_OF_SCROLLS; i++) { - cy.get('#acct_mgmt_data_grid').scrollTo('bottom'); - cy.wait(500); - } - - - // 由於可能在畫面上是 hidden ,這邊使用 exist 而不是使用 visible - cy.contains('.account-cell', TEST_ACCOUNT, { timeout: 10000 }).should('be.exist'); - // 找到包含 TEST_ACCOUNT 字串的 .account-cell 元素 - cy.contains('.account-cell', TEST_ACCOUNT, { timeout: 10000 }) - .parents('tr') // 找到 .account-cell 元素的祖先 tr 元素 - .find('.btn-edit') // 在該 tr 元素內找到 .btn-edit 按鈕 - .should('be.exist') // 驗證該按鈕可見 - .click(); // 點擊該按鈕 - - cy.contains('h1', MODAL_TITLE_ACCOUNT_EDIT).should('be.exist'); - const randomNumber = getRandomInt(1000); - // 將整數轉換為四位數字串,並補零 - const fourDigitString = randomNumber.toString().padStart(4, '0'); - cy.get('#input_name_field').clear().type( TEST_ACCOUNT + '-' + fourDigitString); - cy.get('#input_first_pwd').type('aaaaaa'); - cy.get('#input_second_pwd').type('aaaaaa'); - - cy.contains('button', 'Confirm') - .should('be.visible') - .and('be.enabled') - .click(); - - cy.contains(MSG_ACCOUNT_EDITED).should('be.visible'); //表示帳號創建成功 - }); -}); - \ No newline at end of file diff --git a/cypress/e2e/compare.cy.js b/cypress/e2e/compare.cy.js index 6df5b94..1026c4a 100644 --- a/cypress/e2e/compare.cy.js +++ b/cypress/e2e/compare.cy.js @@ -1,155 +1,75 @@ -describe('Performance', ()=>{ +import { loginWithFixtures } from '../support/intercept'; + +describe('Compare', () => { beforeEach(() => { + loginWithFixtures(); cy.visit('/files'); - cy.login(); - cy.visit('/files'); - cy.url().should('include', 'files'); + cy.wait('@getFiles'); cy.contains('li', 'COMPARE').click(); }); - it('Compare dropdown sorting Test', () => { - const expectedOptions = - ['By File Name (A to Z)', 'By File Name (Z to A)', 'By Dependency (A to Z)', 'By Dependency (Z to A)', 'By File Type (A to Z)', 'By File Type (Z to A)', 'By Last Update (A to Z)', 'By Last Update (Z to A)']; + it('Compare dropdown sorting options', () => { + const expectedOptions = [ + 'By File Name (A to Z)', 'By File Name (Z to A)', + 'By Dependency (A to Z)', 'By Dependency (Z to A)', + 'By File Type (A to Z)', 'By File Type (Z to A)', + 'By Last Update (A to Z)', 'By Last Update (Z to A)', + ]; - cy.url().should('include', 'files'); cy.get('.p-dropdown').click(); cy.get('.p-dropdown-items') - .find('.p-dropdown-item') - .then($options => { - const actualOptions = $options.map((index, elem) => Cypress.$(elem).find('.p-dropdown-item-label').text()).get() - expect(actualOptions).to.deep.equal(expectedOptions) - }) - }) - - it('Grid Card Sorting Test', () => { - let originalData = []; - let sortedData = []; - - cy.url().should('include', 'files'); - cy.get('#compareGridCards').find('li').each($card => { - originalData.push($card.find('div h3').text().trim()); - }); - // Sort: By File Name (A to Z) - cy.get('.p-dropdown').click(); - cy.contains('.p-dropdown-item', 'By File Name (A to Z)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))); - // Sort: By File Name (Z to A) - sortedData = []; - cy.get('.p-dropdown').click(); - cy.contains('.p-dropdown-item', 'By File Name (Z to A)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase())).reverse()); - // Sort: By Dependency (A to Z) - cy.get('.p-dropdown').click(); - sortedData = []; - cy.contains('.p-dropdown-item', 'By Dependency (A to Z)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => a.parentLog.toLowerCase().localeCompare(b.parentLog.toLowerCase()))); - // Sort: By Dependency (Z to A) - cy.get('.p-dropdown').click(); - sortedData = []; - cy.contains('.p-dropdown-item', 'By Dependency (Z to A)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => a.parentLog.toLowerCase().localeCompare(b.parentLog.toLowerCase())).reverse()); - // Sort: By File Type (A to Z) - cy.get('.p-dropdown').click(); - sortedData = []; - cy.contains('.p-dropdown-item', 'By File Type (A to Z)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => a.fileType.toLowerCase().localeCompare(b.fileType.toLowerCase()))); - // Sort: By File Type (Z to A) - cy.get('.p-dropdown').click(); - sortedData = []; - cy.contains('.p-dropdown-item', 'By File Type (Z to A)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => a.fileType.toLowerCase().localeCompare(b.fileType.toLowerCase())).reverse()); - // Sort: By Last Update (A to Z) - cy.get('.p-dropdown').click(); - sortedData = []; - cy.contains('.p-dropdown-item', 'By Last Update (A to Z)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => new Date(a.updated_base) - new Date(b.updated_base))); - // Sort: By Last Update (Z to A) - cy.get('.p-dropdown').click(); - sortedData = []; - cy.contains('.p-dropdown-item', 'By Last Update (Z to A)').click(); - cy.wait(2000); - cy.get('#compareGridCards').find('li').each($card => { - sortedData.push($card.find('div h3').text().trim()); - }); - expect(sortedData).to.deep.equal(originalData.sort((a, b) => new Date(a.updated_base) - new Date(b.updated_base)).reverse()); - }) - - it('Enter Compare', () => { - cy.url().should('include', 'files'); - cy.contains('button', 'Compare').should('be.disabled'); // 斷言按鈕為禁用狀態 - // 安裝套件: cypress-drag-drop - cy.get('#compareFile0').drag('#primaryDragCard'); - cy.get('#compareFile1').drag('#secondaryDragCard'); - // Enter Compare - cy.contains('button', 'Compare').click(); - cy.contains('h2', 'COMPARE'); - cy.url().should('include', 'compare'); - cy.wait(2000); - // 斷言排序 - - // Assert these charts should be visible - cy.get('span').contains('Average Cycle Time').should('exist').and('be.visible'); - cy.get('span').contains('Cycle Efficiency').should('exist').and('be.visible'); - cy.get('span').contains('Average Processing Time').should('exist').and('be.visible'); - cy.get('span').contains('Average Processing Time by Activity').should('exist').and('be.visible'); - cy.get('span').contains('Average Waiting Time').should('exist').and('be.visible'); - cy.get('span').contains('Average Waiting Time between Activity').should('exist').and('be.visible'); - - // 斷言狀態欄 - cy.get('#compareState').click(); - cy.wait(3000); // 等待渲染 - cy.get('.p-sidebar-content li').first().then($li => { - const percentage = $li.find('span').eq(1).text(); - expect(percentage).to.match(/^\d+(\.\d+)?%$/); // 使用正則表達式來確認文字內容是否符合數字 + % 符號的組合 - }); + .find('.p-dropdown-item') + .then($options => { + const actualOptions = $options + .map((index, elem) => Cypress.$(elem).find('.p-dropdown-item-label').text()) + .get(); + expect(actualOptions).to.deep.equal(expectedOptions); + }); }); - it('Anchor', () => { - // enter Map - cy.url().should('include', 'files'); + it('Grid cards are rendered for compare file selection', () => { + cy.get('#compareGridCards').find('li').should('have.length.greaterThan', 0); + }); + + it('Compare button is disabled until two files are dragged', () => { + cy.contains('button', 'Compare').should('be.disabled'); + cy.get('#compareFile0').drag('#primaryDragCard'); + cy.get('#compareFile1').drag('#secondaryDragCard'); + cy.contains('button', 'Compare').should('be.enabled'); + }); + + it('Enter Compare dashboard and see charts', () => { cy.get('#compareFile0').drag('#primaryDragCard'); cy.get('#compareFile1').drag('#secondaryDragCard'); cy.contains('button', 'Compare').click(); - cy.contains('h2', 'COMPARE'); + cy.wait('@getCompare'); cy.url().should('include', 'compare'); - cy.wait(2000); - // Anchor 網頁不會捲動到錨點位置是因為 Cypress 是模擬使用者行為而非準確瀏覽器行為 - cy.get('aside li a[href="#cycleTime"]').click(); - cy.url().should('include', '#cycleTime'); - cy.get('aside li a[href="#processingTime"]').click(); - cy.url().should('include', '#processingTime'); - cy.get('aside li a[href="#waitingTime"]').click(); - cy.url().should('include', '#waitingTime'); - cy.get('aside li a[href="#cases"]').click(); - cy.url().should('include', '#cases'); + + // Assert chart title spans are visible + cy.contains('span', 'Average Cycle Time').should('exist'); + cy.contains('span', 'Cycle Efficiency').should('exist'); + cy.contains('span', 'Average Processing Time').should('exist'); + cy.contains('span', 'Average Processing Time by Activity').should('exist'); + cy.contains('span', 'Average Waiting Time').should('exist'); + cy.contains('span', 'Average Waiting Time between Activity').should('exist'); }); -}) + it('Compare State button exists on dashboard', () => { + cy.get('#compareFile0').drag('#primaryDragCard'); + cy.get('#compareFile1').drag('#secondaryDragCard'); + cy.contains('button', 'Compare').click(); + cy.wait('@getCompare'); + + cy.get('#compareState').should('exist').and('be.visible'); + }); + + it('Sidebar shows time usage and frequency sections', () => { + cy.get('#compareFile0').drag('#primaryDragCard'); + cy.get('#compareFile1').drag('#secondaryDragCard'); + cy.contains('button', 'Compare').click(); + cy.wait('@getCompare'); + + cy.get('aside').should('exist'); + cy.get('aside li').should('have.length.greaterThan', 0); + }); +}); diff --git a/cypress/e2e/pageAdmin.cy.js b/cypress/e2e/pageAdmin.cy.js index 8f27778..c58d088 100644 --- a/cypress/e2e/pageAdmin.cy.js +++ b/cypress/e2e/pageAdmin.cy.js @@ -1,29 +1,32 @@ -describe('Modal dismissal related navigation button test', ()=>{ - beforeEach(() => { - cy.visit('/files'); - cy.login(); - cy.visit('/files'); - }); +import { loginWithFixtures } from '../support/intercept'; - it("On MAP page, after dismissing modal, active page is still MAP page, not CONFORMANCE page.", () => { - cy.get('tbody td').first().dblclick(); - // Should enter MAP page - cy.url().should('include', 'map'); - cy.url().should('include', 'filter'); +describe('Discover page navigation tabs', () => { + beforeEach(() => { + loginWithFixtures(); + cy.visit('/files'); + cy.wait('@getFiles'); + }); - cy.get('canvas').click(); + it('Double-clicking a log file enters the MAP page.', () => { + cy.contains('td.fileName', 'sample-process.xes').dblclick(); + cy.url().should('include', 'map'); + // MAP tab should exist in the navbar + cy.contains('.nav-item', 'MAP').should('exist'); + }); - // At least set a condition as an edit action - cy.get('[type="checkbox"]').first().check(); - cy.contains('button', 'Apply').click(); - cy.get('#tabFunnel').click(); - cy.contains('button', 'Apply All').click(); - cy.contains('.nav-item', 'CONFORMANCE').click(); - // Simulate escape keyboard; simulate dismissal of modal - cy.get('body').trigger('keydown', { keyCode: 27}); - cy.wait(500); - cy.get('body').trigger('keyup', { keyCode: 27}); - cy.get('.nav-item', 'MAP').should('have.class', 'active'); - }); + it('Clicking CONFORMANCE tab switches active page.', () => { + cy.contains('td.fileName', 'sample-process.xes').dblclick(); + cy.url().should('include', 'map'); + cy.contains('.nav-item', 'CONFORMANCE').click(); + cy.url().should('include', 'conformance'); + cy.contains('.nav-item', 'CONFORMANCE').should('have.class', 'active'); + }); + + it('Clicking PERFORMANCE tab switches active page.', () => { + cy.contains('td.fileName', 'sample-process.xes').dblclick(); + cy.url().should('include', 'map'); + cy.contains('.nav-item', 'PERFORMANCE').click(); + cy.url().should('include', 'performance'); + cy.contains('.nav-item', 'PERFORMANCE').should('have.class', 'active'); + }); }); - \ No newline at end of file diff --git a/cypress/e2e/pasteUrlLoginRedirect.cy.js b/cypress/e2e/pasteUrlLoginRedirect.cy.js index f4a4a07..bb3b785 100644 --- a/cypress/e2e/pasteUrlLoginRedirect.cy.js +++ b/cypress/e2e/pasteUrlLoginRedirect.cy.js @@ -1,37 +1,51 @@ -const conformanceExampleUrl = "http://localhost:5173/discover/conformance/log/185524797/conformance"; -const urlUnderTestNotEncoded = `http://localhost:5173/login?return-to=${conformanceExampleUrl}`; -const urlsUnderTest = [urlUnderTestNotEncoded,]; -describe('Conformance url pastetd', ()=>{ - urlsUnderTest.forEach((curUrl) => { - context(`Testing with URL: ${curUrl}`, () => { - beforeEach(() => { - cy.visit(curUrl); - }); +import { setupApiIntercepts } from '../support/intercept'; - it('Positive case: After pasting discover/conformance/log/ page, frontend should redirect to corresponding ' + - 'page, not login page', () => { - cy.get('#account').clear().type(`${Cypress.env('user').username}`); - cy.get('#password').clear().type(`${Cypress.env('user').password}`); - cy.get('.btn-lg').click(); - cy.get('form').submit(); +describe('Paste URL login redirect', () => { + it('After login with return-to param, redirects to the remembered page', () => { + setupApiIntercepts(); - cy.contains('Conformance Checking Results').should('be.visible'); - }); + // Visit login page with a return-to query param (base64-encoded URL) + const targetUrl = 'http://localhost:5173/discover/conformance/log/1/conformance'; + const encodedUrl = btoa(targetUrl); + cy.visit(`/login?return-to=${encodedUrl}`); - it('Negative case: User who is logged out cannout access inner pages', () => { - cy.get('#account').clear().type(`${Cypress.env('user').username}`); - cy.get('#password').clear().type(`${Cypress.env('user').password}`); - cy.get('.btn-lg').click(); - cy.get('form').submit(); + // Fill in login form + cy.get('#account').type('testadmin'); + cy.get('#password').type('password123'); + cy.get('form').submit(); + cy.wait('@postToken'); - cy.get('#logout_btn').click(); + // After login, the app should attempt to redirect to the return-to URL. + // Since window.location.href is used (not router.push), we verify the + // login form disappears and the token cookie is set. + cy.getCookie('luciaToken').should('exist'); + }); - cy.visit(curUrl); + it('Login without return-to param redirects to /files', () => { + setupApiIntercepts(); + cy.visit('/login'); - cy.contains('Account').should('be.visible'); - cy.contains('Password').should('be.visible'); - cy.contains('Conformance Checking Results').should('not.exist'); - }); - }); + cy.get('#account').type('testadmin'); + cy.get('#password').type('password123'); + cy.get('form').submit(); + cy.wait('@postToken'); + + cy.url().should('include', '/files'); + }); + + it('Unauthenticated user cannot access inner pages', () => { + setupApiIntercepts(); + // Override my-account to return 401 (simulate logged-out state) + cy.intercept('GET', '/api/my-account', { + statusCode: 401, + body: { detail: 'Not authenticated' }, + }).as('getMyAccountUnauth'); + + cy.visit('/files'); + + // Should be redirected to login page + cy.url().should('include', '/login'); + cy.get('#account').should('exist'); + cy.get('#password').should('exist'); }); }); diff --git a/cypress/fixtures/api/compare.json b/cypress/fixtures/api/compare.json new file mode 100644 index 0000000..7a4592f --- /dev/null +++ b/cypress/fixtures/api/compare.json @@ -0,0 +1,92 @@ +{ + "time": { + "avg_cycle_time": { + "primary": [ + { "date": "2022-01-15", "value": 500000 }, + { "date": "2022-06-15", "value": 600000 }, + { "date": "2022-12-15", "value": 550000 } + ], + "secondary": [ + { "date": "2022-01-15", "value": 480000 }, + { "date": "2022-06-15", "value": 520000 }, + { "date": "2022-12-15", "value": 510000 } + ] + }, + "avg_cycle_efficiency": { + "primary": [ + { "label": "File A", "value": 0.75 } + ], + "secondary": [ + { "label": "File B", "value": 0.68 } + ] + }, + "avg_process_time": { + "primary": [ + { "date": "2022-01-15", "value": 300000 }, + { "date": "2022-06-15", "value": 350000 }, + { "date": "2022-12-15", "value": 320000 } + ], + "secondary": [ + { "date": "2022-01-15", "value": 280000 }, + { "date": "2022-06-15", "value": 310000 }, + { "date": "2022-12-15", "value": 290000 } + ] + }, + "avg_process_time_by_task": { + "primary": [ + { "label": ["Activity", "A"], "value": 120000 }, + { "label": ["Activity", "B"], "value": 80000 } + ], + "secondary": [ + { "label": ["Activity", "A"], "value": 110000 }, + { "label": ["Activity", "B"], "value": 95000 } + ] + }, + "avg_waiting_time": { + "primary": [ + { "date": "2022-01-15", "value": 200000 }, + { "date": "2022-06-15", "value": 250000 }, + { "date": "2022-12-15", "value": 230000 } + ], + "secondary": [ + { "date": "2022-01-15", "value": 200000 }, + { "date": "2022-06-15", "value": 210000 }, + { "date": "2022-12-15", "value": 220000 } + ] + }, + "avg_waiting_time_by_edge": { + "primary": [ + { "label": ["A", "B"], "value": 150000 }, + { "label": ["B", "C"], "value": 100000 } + ], + "secondary": [ + { "label": ["A", "B"], "value": 140000 }, + { "label": ["B", "C"], "value": 110000 } + ] + } + }, + "freq": { + "cases": { + "primary": [ + { "count": 100 }, + { "count": 120 }, + { "count": 110 } + ], + "secondary": [ + { "count": 95 }, + { "count": 105 }, + { "count": 100 } + ] + }, + "cases_by_task": { + "primary": [ + { "label": ["Activity", "A"], "value": 200 }, + { "label": ["Activity", "B"], "value": 150 } + ], + "secondary": [ + { "label": ["Activity", "A"], "value": 180 }, + { "label": ["Activity", "B"], "value": 160 } + ] + } + } +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js index bf819af..374bf91 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -10,22 +10,9 @@ // -- This is a parent command -- import '@4tw/cypress-drag-drop' -const loginApiUrl = Cypress.env('loginApiUrl'); - Cypress.Commands.add('login', () => { - cy.request({ - method: 'POST', - url: loginApiUrl, - form: true, - body: { - username: Cypress.env('user').username, - password: Cypress.env('user').password, - grant_type: 'password', - } - }).then(response => { - const token = response.body.access_token; - cy.setCookie('luciaToken', token); - }) + cy.setCookie('luciaToken', 'fake-access-token-for-testing'); + cy.setCookie('isLuciaLoggedIn', 'true'); }); // 呼叫方式: cy.login() // -- This is a child command -- diff --git a/cypress/support/intercept.js b/cypress/support/intercept.js index 7a2e896..4960ce7 100644 --- a/cypress/support/intercept.js +++ b/cypress/support/intercept.js @@ -130,6 +130,11 @@ export function setupApiIntercepts() { fixture: 'api/filter-params.json', }).as('getFilterCheckParams'); + // Compare dashboard + cy.intercept('GET', /\/api\/compare\?datasets=/, { + fixture: 'api/compare.json', + }).as('getCompare'); + // Dependents (for delete confirmation) cy.intercept('GET', '/api/logs/*/dependents', { statusCode: 200,