Rewrite old E2E tests to use fixture-based API mocking, eliminating need for real credentials

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 00:31:16 +08:00
parent dc0a98f819
commit 905f546227
12 changed files with 371 additions and 458 deletions

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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'); //表示帳號創建成功
});
});

View File

@@ -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);
});
});

View File

@@ -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');
});
});

View File

@@ -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');
});
});

View File

@@ -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 }
]
}
}
}

View File

@@ -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 --

View File

@@ -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,