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

@@ -10,7 +10,5 @@ module.exports = defineConfig({
specPattern: "cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}", specPattern: "cypress/e2e/**/*.{cy,spec}.{js,jsx,ts,tsx}",
}, },
includeShadowDom: true, includeShadowDom: true,
env: { env: {}
// Removed hardcoded password — use cypress.env.json for real credentials
}
}); });

View File

@@ -1,71 +1,52 @@
import { loginWithFixtures } from '../../support/intercept';
const MSG_ACCOUNT_NOT_UNIQUE = 'Account has already been registered.'; const MSG_ACCOUNT_NOT_UNIQUE = 'Account has already been registered.';
describe('Account duplication check.', ()=>{ describe('Account duplication check.', () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/account-admin'); loginWithFixtures();
const username = Cypress.env('user').username; cy.visit('/account-admin');
const password = Cypress.env('user').password; cy.wait('@getUsers');
});
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('When an account is already existed, show the error message when the confirm button is clicked.', () => { it('When an account already exists, show error message on confirm.', () => {
const testAccountName = '000000'; 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'); //表示帳號創建成功
cy.contains('span', 'Account').should('be.visible'); // 表示畫面轉導向成功,出現清單列表 // First creation: account doesn't exist yet
// 接著,點選排序按鈕,排序將會造成生升冪排序,我們希望 000000 帳號排在第一個 cy.intercept('GET', '/api/users/000000', {
// 找到包含 'Account' 的 span 元素 statusCode: 404,
cy.get('span:contains("Account")') body: { detail: 'Not found' },
.closest('th') }).as('checkNewUser');
.find('span')
.not(':contains("Account")')
.first()
.click(); // 點選下一个兄弟 span 元素
// 等待,以確保頁面已完全渲染
cy.wait(1000);
// 確認 000000 帳號是否出現在第一列 cy.contains('button', 'Create New').should('be.visible').click();
cy.get('tr').filter((index, tr) => { cy.get('#input_account_field').type(testAccountName);
// 遍歷所有的 tr 元素 cy.get('#input_name_field').type(testAccountName);
const td = Cypress.$(tr).find('td').eq(0); // 找到第一個 td 元素 cy.get('#input_first_pwd').type(testAccountName);
return td.text().trim() === '000000'; // 檢查其文本內容是否為 000000 cy.get('.checkbox-and-text').first().find('div').first().click();
}).first().should('exist'); // 確認至少有一個符合條件的 tr 存在
// 接著又再次創建一個同樣名稱的帳號,企圖看是否出現錯誤訊息 cy.contains('button', 'Confirm')
cy.contains('button', 'Create New').should('be.visible'); .should('be.visible')
cy.contains('button', 'Create New').click(); .and('be.enabled')
.click();
// 在 id 為 input_account_field 的 input 元素內填入值 cy.wait('@postUser');
cy.get('#input_account_field').type(testAccountName); cy.contains('Account added').should('be.visible');
cy.get('#input_name_field').type(testAccountName);
cy.get('#input_first_pwd').type(testAccountName); // Second creation: now account exists — override to return 200
cy.get('#input_second_pwd').type(testAccountName); cy.intercept('GET', '/api/users/000000', {
cy.get('.checkbox-and-text').first().find('div').first().click(); statusCode: 200,
// 確保 Confirm 按鈕存在並可點擊 body: { username: '000000', name: '000000', is_admin: false, is_active: true, roles: [] },
cy.contains('button', 'Confirm') }).as('checkExistingUser');
.should('be.visible')
.and('be.enabled') cy.contains('button', 'Create New').should('be.visible').click();
.click(); cy.get('#input_account_field').type(testAccountName);
cy.contains('span', MSG_ACCOUNT_NOT_UNIQUE).should('be.visible'); 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'; import { loginWithFixtures } from '../../support/intercept';
const MSG_PWD_NOT_MATCHED = 'Confirm Password does not match.';
describe('Confirm that two input passwords are equal.', ()=>{ describe('Password validation on create account.', () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/account-admin'); loginWithFixtures();
const username = Cypress.env('user').username; cy.visit('/account-admin');
const password = Cypress.env('user').password; cy.wait('@getUsers');
});
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('Confirm that when creating an account, the two input password fields are equal; otherwise, show the message.', () => { it('When password is too short, confirm button stays disabled.', () => {
cy.contains('button', 'Create New').should('be.visible'); cy.contains('button', 'Create New').should('be.visible').click();
cy.contains('button', 'Create New').click();
const randomNumber = getRandomInt(1000);
// 將整數轉換為四位數字串,並補零
const fourDigitString = randomNumber.toString().padStart(4, '0');
// 將 'unit-test-' 和生成的四位數字串組合 cy.get('#input_account_field').type('unit-test-0001');
const inputValue = `unit-test-${fourDigitString}`; 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.contains('button', 'Confirm').should('be.disabled');
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');
// 確保 Confirm 按鈕存在並可點擊 it('When password meets minimum length, confirm button enables.', () => {
cy.contains('button', 'Confirm') cy.contains('button', 'Create New').should('be.visible').click();
.should('be.visible')
.and('be.enabled')
.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', ()=>{ describe('Create an Account', () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/account-admin'); loginWithFixtures();
const username = Cypress.env('user').username; // Override: new usernames should return 404 (account doesn't exist yet)
const password = Cypress.env('user').password; cy.intercept('GET', '/api/users/unit-test-*', {
statusCode: 404,
cy.visit('/account-admin'); body: { detail: 'Not found' },
}).as('checkNewUser');
cy.get('input[id="account"]').type(username); cy.visit('/account-admin');
cy.get('input[id="password"]').type(password); cy.wait('@getUsers');
cy.get('button[type="submit"]').click(); });
});
it('Create a new account; role is admin; should appear Saved message', () => { it('Create a new account with admin role; should show saved message.', () => {
cy.contains('button', 'Create New').should('be.visible'); cy.contains('button', 'Create New').should('be.visible').click();
cy.contains('button', 'Create New').click();
const randomNumber = getRandomInt(1000);
// 將整數轉換為四位數字串,並補零
const fourDigitString = randomNumber.toString().padStart(4, '0');
// 將 'unit-test-' 和生成的四位數字串組合 cy.get('#input_account_field').type('unit-test-0001');
const inputValue = `unit-test-${fourDigitString}`; 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.contains('button', 'Confirm')
cy.get('#input_account_field').type(inputValue); .should('be.visible')
cy.get('#input_name_field').type(inputValue); .and('be.enabled')
cy.get('#input_first_pwd').type('aaaaaa'); .click();
cy.get('#input_second_pwd').type('aaaaaa'); cy.wait('@postUser');
cy.get('.checkbox-and-text').first().find('div').first().click(); cy.contains('Account added').should('be.visible');
// 確保 Confirm 按鈕存在並可點擊 });
cy.contains('button', 'Confirm')
.should('be.visible') it('Confirm button is disabled when required fields are empty.', () => {
.and('be.enabled') cy.contains('button', 'Create New').should('be.visible').click();
.click(); cy.get('#input_account_field').type('test');
cy.contains('Account added').should('be.visible'); //表示帳號創建成功 cy.contains('button', 'Confirm').should('be.disabled');
}); });
}); });

View File

@@ -1,57 +1,24 @@
describe('Delete an Account', ()=>{ import { loginWithFixtures } from '../../support/intercept';
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('Delete an account just created, which is named 000000.', () => { describe('Delete an Account', () => {
cy.contains('button', 'Create New').should('be.visible'); beforeEach(() => {
cy.contains('button', 'Create New').click(); loginWithFixtures();
cy.visit('/account-admin');
// 在 id 為 input_account_field 的 input 元素內填入值 cy.wait('@getUsers');
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'); //表示帳號創建成功
cy.contains('span', 'Account').should('be.visible'); // 表示畫面轉導向成功,出現清單列表 it('Delete button opens confirmation modal and deletes on confirm.', () => {
// 接著,點選排序按鈕,排序將會造成生升冪排序,我們希望 000000 帳號排在第一個 cy.get('img.delete-account').first().click();
// 找到包含 'Account' 的 span 元素 cy.contains('ARE YOU SURE TO DELETE').should('be.visible');
cy.get('span:contains("Account")') cy.get('#sure_to_delete_acct_btn').click();
.closest('th') cy.wait('@deleteUser');
.find('span') cy.contains('Account deleted').should('be.visible');
.not(':contains("Account")') });
.first()
.click(); // 點選下一个兄弟 span 元素
// 等待,以確保頁面已完全渲染
cy.wait(1000);
// 確認 000000 帳號是否出現在第一列 it('Cancel button closes the delete confirmation modal.', () => {
cy.get('tr').filter((index, tr) => { cy.get('img.delete-account').first().click();
// 遍歷所有的 tr 元素 cy.contains('ARE YOU SURE TO DELETE').should('be.visible');
const td = Cypress.$(tr).find('td').eq(0); // 找到第一個 td 元素 cy.get('#calcel_delete_acct_btn').click();
return td.text().trim() === '000000'; // 檢查其文本內容是否為 000000 cy.contains('ARE YOU SURE TO DELETE').should('not.exist');
}).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'); //表示帳號刪除成功
});
}); });

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 MODAL_TITLE_ACCOUNT_EDIT = 'Account Edit';
const MSG_ACCOUNT_EDITED = 'Saved'; const MSG_ACCOUNT_EDITED = 'Saved';
const NUM_OF_SCROLLS = 5;
Cypress.Commands.add('manualScrollToBottom', (repeats = 20) => { describe('Edit an account', () => {
for (let i = 0; i < repeats; i++) { beforeEach(() => {
cy.scrollTo('bottom', { duration: 500 }); loginWithFixtures();
cy.wait(500); 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(() => { beforeEach(() => {
loginWithFixtures();
cy.visit('/files'); cy.visit('/files');
cy.login(); cy.wait('@getFiles');
cy.visit('/files');
cy.url().should('include', 'files');
cy.contains('li', 'COMPARE').click(); cy.contains('li', 'COMPARE').click();
}); });
it('Compare dropdown sorting Test', () => { it('Compare dropdown sorting options', () => {
const expectedOptions = 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)']; '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').click();
cy.get('.p-dropdown-items') cy.get('.p-dropdown-items')
.find('.p-dropdown-item') .find('.p-dropdown-item')
.then($options => { .then($options => {
const actualOptions = $options.map((index, elem) => Cypress.$(elem).find('.p-dropdown-item-label').text()).get() const actualOptions = $options
expect(actualOptions).to.deep.equal(expectedOptions) .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+)?%$/); // 使用正則表達式來確認文字內容是否符合數字 + % 符號的組合
});
}); });
it('Anchor', () => { it('Grid cards are rendered for compare file selection', () => {
// enter Map cy.get('#compareGridCards').find('li').should('have.length.greaterThan', 0);
cy.url().should('include', 'files'); });
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('#compareFile0').drag('#primaryDragCard');
cy.get('#compareFile1').drag('#secondaryDragCard'); cy.get('#compareFile1').drag('#secondaryDragCard');
cy.contains('button', 'Compare').click(); cy.contains('button', 'Compare').click();
cy.contains('h2', 'COMPARE'); cy.wait('@getCompare');
cy.url().should('include', 'compare'); cy.url().should('include', 'compare');
cy.wait(2000);
// Anchor 網頁不會捲動到錨點位置是因為 Cypress 是模擬使用者行為而非準確瀏覽器行為 // Assert chart title spans are visible
cy.get('aside li a[href="#cycleTime"]').click(); cy.contains('span', 'Average Cycle Time').should('exist');
cy.url().should('include', '#cycleTime'); cy.contains('span', 'Cycle Efficiency').should('exist');
cy.get('aside li a[href="#processingTime"]').click(); cy.contains('span', 'Average Processing Time').should('exist');
cy.url().should('include', '#processingTime'); cy.contains('span', 'Average Processing Time by Activity').should('exist');
cy.get('aside li a[href="#waitingTime"]').click(); cy.contains('span', 'Average Waiting Time').should('exist');
cy.url().should('include', '#waitingTime'); cy.contains('span', 'Average Waiting Time between Activity').should('exist');
cy.get('aside li a[href="#cases"]').click();
cy.url().should('include', '#cases');
}); });
}) 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', ()=>{ import { loginWithFixtures } from '../support/intercept';
beforeEach(() => {
cy.visit('/files');
cy.login();
cy.visit('/files');
});
it("On MAP page, after dismissing modal, active page is still MAP page, not CONFORMANCE page.", () => { describe('Discover page navigation tabs', () => {
cy.get('tbody td').first().dblclick(); beforeEach(() => {
// Should enter MAP page loginWithFixtures();
cy.url().should('include', 'map'); cy.visit('/files');
cy.url().should('include', 'filter'); 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 it('Clicking CONFORMANCE tab switches active page.', () => {
cy.get('[type="checkbox"]').first().check(); cy.contains('td.fileName', 'sample-process.xes').dblclick();
cy.contains('button', 'Apply').click(); cy.url().should('include', 'map');
cy.get('#tabFunnel').click(); cy.contains('.nav-item', 'CONFORMANCE').click();
cy.contains('button', 'Apply All').click(); cy.url().should('include', 'conformance');
cy.contains('.nav-item', 'CONFORMANCE').click(); cy.contains('.nav-item', 'CONFORMANCE').should('have.class', 'active');
// Simulate escape keyboard; simulate dismissal of modal });
cy.get('body').trigger('keydown', { keyCode: 27});
cy.wait(500); it('Clicking PERFORMANCE tab switches active page.', () => {
cy.get('body').trigger('keyup', { keyCode: 27}); cy.contains('td.fileName', 'sample-process.xes').dblclick();
cy.get('.nav-item', 'MAP').should('have.class', 'active'); 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"; import { setupApiIntercepts } from '../support/intercept';
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);
});
it('Positive case: After pasting discover/conformance/log/ page, frontend should redirect to corresponding ' + describe('Paste URL login redirect', () => {
'page, not login page', () => { it('After login with return-to param, redirects to the remembered page', () => {
cy.get('#account').clear().type(`${Cypress.env('user').username}`); setupApiIntercepts();
cy.get('#password').clear().type(`${Cypress.env('user').password}`);
cy.get('.btn-lg').click();
cy.get('form').submit();
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', () => { // Fill in login form
cy.get('#account').clear().type(`${Cypress.env('user').username}`); cy.get('#account').type('testadmin');
cy.get('#password').clear().type(`${Cypress.env('user').password}`); cy.get('#password').type('password123');
cy.get('.btn-lg').click(); cy.get('form').submit();
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.get('#account').type('testadmin');
cy.contains('Password').should('be.visible'); cy.get('#password').type('password123');
cy.contains('Conformance Checking Results').should('not.exist'); 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 -- // -- This is a parent command --
import '@4tw/cypress-drag-drop' import '@4tw/cypress-drag-drop'
const loginApiUrl = Cypress.env('loginApiUrl');
Cypress.Commands.add('login', () => { Cypress.Commands.add('login', () => {
cy.request({ cy.setCookie('luciaToken', 'fake-access-token-for-testing');
method: 'POST', cy.setCookie('isLuciaLoggedIn', 'true');
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.login() // 呼叫方式: cy.login()
// -- This is a child command -- // -- This is a child command --

View File

@@ -130,6 +130,11 @@ export function setupApiIntercepts() {
fixture: 'api/filter-params.json', fixture: 'api/filter-params.json',
}).as('getFilterCheckParams'); }).as('getFilterCheckParams');
// Compare dashboard
cy.intercept('GET', /\/api\/compare\?datasets=/, {
fixture: 'api/compare.json',
}).as('getCompare');
// Dependents (for delete confirmation) // Dependents (for delete confirmation)
cy.intercept('GET', '/api/logs/*/dependents', { cy.intercept('GET', '/api/logs/*/dependents', {
statusCode: 200, statusCode: 200,