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:
@@ -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: {}
|
||||
});
|
||||
|
||||
@@ -1,71 +1,52 @@
|
||||
import { loginWithFixtures } from '../../support/intercept';
|
||||
|
||||
const MSG_ACCOUNT_NOT_UNIQUE = 'Account has already been registered.';
|
||||
|
||||
describe('Account duplication check.', ()=>{
|
||||
describe('Account duplication check.', () => {
|
||||
beforeEach(() => {
|
||||
loginWithFixtures();
|
||||
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();
|
||||
cy.wait('@getUsers');
|
||||
});
|
||||
|
||||
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';
|
||||
cy.contains('button', 'Create New').should('be.visible');
|
||||
cy.contains('button', 'Create New').click();
|
||||
|
||||
// 在 id 為 input_account_field 的 input 元素內填入值
|
||||
// First creation: account doesn't exist yet
|
||||
cy.intercept('GET', '/api/users/000000', {
|
||||
statusCode: 404,
|
||||
body: { detail: 'Not found' },
|
||||
}).as('checkNewUser');
|
||||
|
||||
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('#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.wait('@postUser');
|
||||
cy.contains('Account added').should('be.visible');
|
||||
|
||||
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 元素
|
||||
// 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.wait(1000);
|
||||
|
||||
// 確認 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');
|
||||
cy.contains('button', 'Create New').click();
|
||||
|
||||
// 在 id 為 input_account_field 的 input 元素內填入值
|
||||
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('#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(MSG_ACCOUNT_NOT_UNIQUE).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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.', ()=>{
|
||||
describe('Password validation on create account.', () => {
|
||||
beforeEach(() => {
|
||||
loginWithFixtures();
|
||||
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();
|
||||
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();
|
||||
it('When password is too short, confirm button stays disabled.', () => {
|
||||
cy.contains('button', 'Create New').should('be.visible').click();
|
||||
|
||||
const randomNumber = getRandomInt(1000);
|
||||
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');
|
||||
|
||||
// 將整數轉換為四位數字串,並補零
|
||||
const fourDigitString = randomNumber.toString().padStart(4, '0');
|
||||
cy.contains('button', 'Confirm').should('be.disabled');
|
||||
});
|
||||
|
||||
// 將 'unit-test-' 和生成的四位數字串組合
|
||||
const inputValue = `unit-test-${fourDigitString}`;
|
||||
it('When password meets minimum length, confirm button enables.', () => {
|
||||
cy.contains('button', 'Create New').should('be.visible').click();
|
||||
|
||||
// 在 id 為 input_account_field 的 input 元素內填入值
|
||||
cy.get('#input_account_field').type(inputValue);
|
||||
cy.get('#input_name_field').type(inputValue);
|
||||
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('#input_second_pwd').type('bbbbbb');
|
||||
|
||||
// 確保 Confirm 按鈕存在並可點擊
|
||||
cy.contains('button', 'Confirm')
|
||||
.should('be.visible')
|
||||
.and('be.enabled')
|
||||
.click();
|
||||
|
||||
cy.contains(MSG_PWD_NOT_MATCHED).should('be.visible');
|
||||
cy.contains('button', 'Confirm').should('be.enabled');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,42 +1,36 @@
|
||||
import { getRandomInt } from '../../../src/utils/jsUtils';
|
||||
import { loginWithFixtures } from '../../support/intercept';
|
||||
|
||||
describe('Create an Account', ()=>{
|
||||
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');
|
||||
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();
|
||||
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();
|
||||
it('Create a new account with admin role; should show saved message.', () => {
|
||||
cy.contains('button', 'Create New').should('be.visible').click();
|
||||
|
||||
const randomNumber = getRandomInt(1000);
|
||||
|
||||
// 將整數轉換為四位數字串,並補零
|
||||
const fourDigitString = randomNumber.toString().padStart(4, '0');
|
||||
|
||||
// 將 'unit-test-' 和生成的四位數字串組合
|
||||
const inputValue = `unit-test-${fourDigitString}`;
|
||||
|
||||
// 在 id 為 input_account_field 的 input 元素內填入值
|
||||
cy.get('#input_account_field').type(inputValue);
|
||||
cy.get('#input_name_field').type(inputValue);
|
||||
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('#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.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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,57 +1,24 @@
|
||||
describe('Delete an Account', ()=>{
|
||||
import { loginWithFixtures } from '../../support/intercept';
|
||||
|
||||
describe('Delete an Account', () => {
|
||||
beforeEach(() => {
|
||||
loginWithFixtures();
|
||||
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();
|
||||
cy.wait('@getUsers');
|
||||
});
|
||||
|
||||
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'); //表示帳號創建成功
|
||||
|
||||
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);
|
||||
|
||||
// 確認 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 存在
|
||||
|
||||
it('Delete button opens confirmation modal and deletes on confirm.', () => {
|
||||
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('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');
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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', ()=>{
|
||||
describe('Edit an account', () => {
|
||||
beforeEach(() => {
|
||||
loginWithFixtures();
|
||||
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();
|
||||
cy.wait('@getUsers');
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
cy.get('.btn-edit').first().click();
|
||||
cy.wait('@getUserDetail');
|
||||
|
||||
|
||||
// 由於可能在畫面上是 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('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.contains(MSG_ACCOUNT_EDITED).should('be.visible'); //表示帳號創建成功
|
||||
cy.wait('@putUser');
|
||||
cy.contains(MSG_ACCOUNT_EDITED).should('be.visible');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
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 = [];
|
||||
it('Grid cards are rendered for compare file selection', () => {
|
||||
cy.get('#compareGridCards').find('li').should('have.length.greaterThan', 0);
|
||||
});
|
||||
|
||||
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
|
||||
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');
|
||||
// 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+)?%$/); // 使用正則表達式來確認文字內容是否符合數字 + % 符號的組合
|
||||
});
|
||||
cy.contains('button', 'Compare').should('be.enabled');
|
||||
});
|
||||
|
||||
it('Anchor', () => {
|
||||
// enter Map
|
||||
cy.url().should('include', 'files');
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,29 +1,32 @@
|
||||
describe('Modal dismissal related navigation button test', ()=>{
|
||||
import { loginWithFixtures } from '../support/intercept';
|
||||
|
||||
describe('Discover page navigation tabs', () => {
|
||||
beforeEach(() => {
|
||||
loginWithFixtures();
|
||||
cy.visit('/files');
|
||||
cy.login();
|
||||
cy.visit('/files');
|
||||
cy.wait('@getFiles');
|
||||
});
|
||||
|
||||
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
|
||||
it('Double-clicking a log file enters the MAP page.', () => {
|
||||
cy.contains('td.fileName', 'sample-process.xes').dblclick();
|
||||
cy.url().should('include', 'map');
|
||||
cy.url().should('include', 'filter');
|
||||
// MAP tab should exist in the navbar
|
||||
cy.contains('.nav-item', 'MAP').should('exist');
|
||||
});
|
||||
|
||||
cy.get('canvas').click();
|
||||
|
||||
// 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();
|
||||
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();
|
||||
// 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');
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
describe('Paste URL login redirect', () => {
|
||||
it('After login with return-to param, redirects to the remembered page', () => {
|
||||
setupApiIntercepts();
|
||||
|
||||
// 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}`);
|
||||
|
||||
// Fill in login form
|
||||
cy.get('#account').type('testadmin');
|
||||
cy.get('#password').type('password123');
|
||||
cy.get('form').submit();
|
||||
cy.wait('@postToken');
|
||||
|
||||
cy.contains('Conformance Checking Results').should('be.visible');
|
||||
// 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');
|
||||
});
|
||||
|
||||
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();
|
||||
it('Login without return-to param redirects to /files', () => {
|
||||
setupApiIntercepts();
|
||||
cy.visit('/login');
|
||||
|
||||
cy.get('#account').type('testadmin');
|
||||
cy.get('#password').type('password123');
|
||||
cy.get('form').submit();
|
||||
cy.wait('@postToken');
|
||||
|
||||
cy.get('#logout_btn').click();
|
||||
|
||||
cy.visit(curUrl);
|
||||
|
||||
cy.contains('Account').should('be.visible');
|
||||
cy.contains('Password').should('be.visible');
|
||||
cy.contains('Conformance Checking Results').should('not.exist');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
92
cypress/fixtures/api/compare.json
Normal file
92
cypress/fixtures/api/compare.json
Normal 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 }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 --
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user