diff --git a/cypress/e2e/SmartSplit-E2E.cy.js b/cypress/e2e/SmartSplit-E2E.cy.js index e39cefa..56a2430 100644 --- a/cypress/e2e/SmartSplit-E2E.cy.js +++ b/cypress/e2e/SmartSplit-E2E.cy.js @@ -6,7 +6,7 @@ describe('Sign In , Sign Up and Sign Out', () => { cy.existAccount_signUp('MxnlodySoTest', 'TestCypress@example.com', '1234567890', '123456', '123456'); }); it('TC 3 : Sign in with valid account', () => { - cy.signIn('man@example.com', '123456'); + cy.signIn('nicetest@gmail.com', 'gg'); }); it('TC 4 : Sign in with invalid account', () => { cy.invalid_signIn('abc@example.com', '123456'); @@ -18,7 +18,7 @@ describe('Sign In , Sign Up and Sign Out', () => { describe('Profile Editing', () => { beforeEach(() => { - cy.signIn('man@example.com', '123456'); + cy.signIn('nicetest@gmail.com', 'gg'); }); it('TC 6 : Edit profile', () => { cy.editProfile('Man-DevOps', '9876543210'); @@ -27,11 +27,11 @@ describe('Profile Editing', () => { describe('Group Management (CRUD)', () => { beforeEach(() => { - cy.signIn('man@example.com', '123456'); + cy.signIn('testcy@gmail.com', 'gg'); }); it('TC 7 : Create new group', () => { - cy.createNewGroup('Cypress Add new Group', ['NavadolSom', 'SukiKana']); - cy.createNewGroup('For delete Testing', ['NavadolSom']); + cy.createNewGroup('Cypress Add new Group', ['testest', 'MxnlodySoTest']); + cy.createNewGroup('For delete Testing', ['testest']); }); it('TC 8 : Read group', () => { cy.readGroup('Cypress Add new Group'); @@ -46,7 +46,7 @@ describe('Group Management (CRUD)', () => { describe('Smart Split Functionality (Expense)', () => { beforeEach(() => { - cy.signIn('man@example.com', '123456'); + cy.signIn('testcy@gmail.com', 'gg'); }) it('TC 11 : Add expense in group with Equality split', () => { cy.addExpenseEqualSplit('We are BanYai', 'About us 10/10/2025', '1500'); @@ -56,11 +56,14 @@ describe('Smart Split Functionality (Expense)', () => { }) }); + + describe('Smart Split Functionality (Payment)', () => { beforeEach(() => { - cy.signIn('navadol@example.com', '123456'); + cy.visit('/login'); + cy.signIn('TestCypress@example.com', '123456'); }) - it('TC 13 : Expense Payment from Dashboard ', () => { + it('TC 13 : Expense Payment from Dashboard ', () => { cy.payExpenseFromDashboard("5 Star Restuarant") }) it('TC 14 : Expense Payment from Group', () => { @@ -70,9 +73,57 @@ describe('Smart Split Functionality (Payment)', () => { describe('Smart Split Functionality (Verify Payment)', () => { beforeEach(() => { - cy.signIn('man@example.com', '123456'); + cy.signIn('testcy@gmail.com', 'gg'); }) it('TC 15 : Confirm Payment', () => { - cy.conFirmExpensePay("We are BanYai", "5 Star Restuarant", ["NavadolSom"]); + cy.conFirmExpensePay("We are BanYai", "5 Star Restuarant", ["MxnlodySoTest"]); }) }) + +describe('Add user from another group', () => { + beforeEach(() => { + cy.signIn('testcy@gmail.com', 'gg'); + }) + it('TC 16 : Create new group', () => { + cy.createNewGroupAnotheruser('Cypress Add new User from anotherGr', ['Man-DevOps'],'We are BanYai'); + }); +}) + +describe('Check name invalid input', () => { + beforeEach(() => { + cy.signIn('testcy@gmail.com', 'gg'); + }) + it('TC 16 : InvalidInput', () => { + cy.InvalidInput('CheckInvalidInput', 'Mxn-DevOps'); + }); +}) + +describe('Check Bill TOtal', () => { + beforeEach(() => { + cy.signIn('testcy@gmail.com', 'gg'); + }) + it('TC 17 : Check Bill TOtal', () => { + cy.checkTotalbill('We are BanYai', 'About us 10/10/2025'); + }); +}) + +describe('Add expense USD currency & Set ExchageRate (Payment)', () => { + beforeEach(() => { + cy.signIn('TestCypress@example.com', '123456'); + }) + it('TC 18 : Add expense USD currency', () => { + cy.addcurrencyUSD('We are BanYai', 'About us 10/10/2025', '26'); + }) + it('TC 19 : Set ExchageRate', () => { + cy.setExchageRate('We are BanYai', 'About us 10/10/2025', '26','30'); + }) +}); + +describe('Add expense Another currency (Payment)', () => { + beforeEach(() => { + cy.signIn('TestCypress@example.com', '123456'); + }) + it('TC 20 : Add expense Another currency', () => { + cy.addAnothercurrency('We are BanYai', 'About us 10/10/2025','10', 'EUR'); + }) +}); \ No newline at end of file diff --git a/cypress/locators/equalSplitLocator.js b/cypress/locators/equalSplitLocator.js index ccb6d2b..17b8203 100644 --- a/cypress/locators/equalSplitLocator.js +++ b/cypress/locators/equalSplitLocator.js @@ -1,5 +1,5 @@ export const EquallySplitLocator = { ExpenseNameInputBox : '//*[@id="root"]/div/div/input[1]', TotalAmountInputBox : '//*[@id="root"]/div/div/input[2]', - ExpandPaticipantButton : '//*[@id="root"]/div/div/div[2]/button', + ExpandPaticipantButton : '//*[@id="root"]/div/div/button[2]', } \ No newline at end of file diff --git a/cypress/locators/groupAddLocator.js b/cypress/locators/groupAddLocator.js index b21c7d6..33cc8fb 100644 --- a/cypress/locators/groupAddLocator.js +++ b/cypress/locators/groupAddLocator.js @@ -5,6 +5,7 @@ export const GroupAddLocator = { participantAppearName : '//ul//li[normalize-space(text())="${paticipant[i]}"', createGroupButton: '//*[@id="root"]/div/div/div/div/div/button', groupNameHeader: '//*[@id="root"]/div/div/div[1]/div/h2', - saveChangeButton: '//*[@id="root"]/div/div/div[2]/div/button' - + saveChangeButton: '//*[@id="root"]/div/div/div[2]/div/button', + praticipantAnotherInput:'//*[@id="groupSearch"]', + praticipantAnotherClick:'//*[@id="root"]/div/div/div/div/div/div[4]/div/ul/li[1]' } \ No newline at end of file diff --git a/cypress/locators/groupEditLocator.js b/cypress/locators/groupEditLocator.js new file mode 100644 index 0000000..d860843 --- /dev/null +++ b/cypress/locators/groupEditLocator.js @@ -0,0 +1,3 @@ +export const GroupEditLocator = { + groupEditButton:'//*[@id="root"]/div/div/div/div/div/a/div/div[2]/button[2]', +} \ No newline at end of file diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 9c5d0fa..964a6e6 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -12,26 +12,23 @@ import { ManualSplitLocator } from "../locators/manualSplitLocator.js"; Cypress.Commands.add("signIn", (email, password) => { cy.log(`🔍 [ Start ] Sign in with Valid Email `); cy.log(`⚠️ [ Expected ] : Sign-in Successfully`); - cy.session( - [email, password], - () => { - cy.visit(WebPageLocators.loginPage); - cy.get(LoginLocators.usernameInput).type(email); - cy.get(LoginLocators.passwordInput).type(password); - cy.xpath(LoginLocators.submitButton).click(); - cy.url().should("include", WebPageLocators.homePage); - }, - { - cacheAcrossSpecs: true, - } - ); - cy.log(`✅ [ Actual ] : Sign-in Successfully`); + // --- ส่วนการทำงานจริง (ถอดออกมาจาก cy.session) --- + cy.visit(WebPageLocators.loginPage); + cy.get(LoginLocators.usernameInput).type(email); + cy.get(LoginLocators.passwordInput).type(password); + cy.xpath(LoginLocators.submitButton).click(); + + // รอให้ URL เปลี่ยนเพื่อยืนยันว่าเข้าได้จริง + cy.url().should("include", WebPageLocators.homePage); + // ----------------------------------------------- + + cy.log(`✅ [ Actual ] : Sign-in Successfully`); }); Cypress.Commands.add("signOut", () => { cy.visit(WebPageLocators.loginPage); - cy.get(LoginLocators.usernameInput).type('man@example.com'); - cy.get(LoginLocators.passwordInput).type('123456'); + cy.get(LoginLocators.usernameInput).type('nicetest@gmail.com'); + cy.get(LoginLocators.passwordInput).type('gg'); cy.xpath(LoginLocators.submitButton).click(); cy.wait(6000) cy.get('[data-cy="User-account"]').click(); @@ -159,8 +156,8 @@ Cypress.Commands.add( ).click(); } } - cy.xpath('//button[text()="FINISH"]').click(); - cy.xpath('//*[@id="root"]/div/div/div[1]/h1').should( + // cy.xpath('//button[text()="FINISH"]').click(); + cy.xpath('//*[@id="root"]/div/div/div[1]/div/h1').should( "contain.text", "Bill Detail" ); @@ -173,13 +170,13 @@ Cypress.Commands.add("addExpenseManualSplit", (groupName, expenseName) => { method: "Equal", amount: "1500", itemname: "อาหาร", - participants: ["NavadolSom", "SukiKana"], + participants: ['testest', 'MxnlodySoTest'], }, { method: "Percentage", amount: "1500", itemname: "Alcohol", - participants: ["NavadolSom", "SukiKana"], + participants: ['testest', 'MxnlodySoTest'], percent: [25, 25], }, ]; @@ -204,16 +201,16 @@ Cypress.Commands.add("addExpenseManualSplit", (groupName, expenseName) => { .clear() .type(item.itemname); cy.xpath( - `.//summary[.//span[normalize-space(text())="Participants"]]` - ).click(); + `/html/body/div/div/div/div[3]/div/div[4]/div/div/button` + ).last().click(); item.participants.forEach((p) => { cy.xpath( `.//label[.//span[normalize-space(text())="${p}"]]//input[@type="checkbox"]` ).check({ force: true }); }); cy.xpath( - `.//summary[.//span[contains(normalize-space(.),"Shared with")]]` - ).click(); + `//*[@id="root"]/div/div/div[3]/div/div[4]/div/div/button/span[2]` + ).last().click(); if (item.method === "Percentage" && item.percent) { item.percent.forEach((value, i) => { cy.xpath(`(.//input[@type="number"])[${i + 2}]`) @@ -228,11 +225,11 @@ Cypress.Commands.add("addExpenseManualSplit", (groupName, expenseName) => { }); Cypress.Commands.add("payExpenseFromDashboard", (expenseName) => { cy.visit(WebPageLocators.homePage); - + cy.wait(10000) cy.xpath( `//div[contains(@class,"flex items-center justify-between")][.//p[contains(., "${expenseName}")]]//button[contains(., "Pay")]` ) - .should("be.visible") + .should("be.visible").first() .click(); cy.get('input[type="file"]').attachFile("Mockreciept.png"); cy.xpath('//*[@id="root"]/div/div/div[2]/button').click(); @@ -240,7 +237,7 @@ Cypress.Commands.add("payExpenseFromDashboard", (expenseName) => { cy.xpath( `//div[contains(@class,"flex items-center justify-between")][.//p[contains(., "${expenseName}")]]//button[contains(., "Pay")]` ) - .should("be.visible") + .should("be.visible").first() .click(); cy.contains("Pending Payment Exists").should("be.visible"); }); @@ -250,19 +247,21 @@ Cypress.Commands.add("payExpenseFromGroup", (groupName, expenseName) => { `//h2[contains(normalize-space(.),"${groupName.trim()}")]//ancestor::a//button[contains(text(),"View")]`, { timeout: 10000 } ) - .should("be.visible") + .should("be.visible").first() .click(); cy.xpath( `//div[contains(@class,"flex items-center justify-between")][.//h3[contains(.,"${expenseName.trim()}")]]//button[contains(., "Detail")]` ) - .should("be.visible") + .should("be.visible").first() .click(); cy.xpath( - `//div[contains(@class,"flex items-center justify-between")]//button[contains(., "Pay")]` + `//div[contains(@class,"flex items-center justify-between")]//button[contains(., "Pay")]`, + { timeout: 10000 } ) - .should("be.visible") + .should("be.visible").first() .click(); + cy.wait(10000); cy.get('input[type="file"]').attachFile("Mockreciept.png"); cy.xpath('//*[@id="root"]/div/div/div[2]/button').click(); @@ -271,56 +270,194 @@ Cypress.Commands.add("payExpenseFromGroup", (groupName, expenseName) => { `//h2[contains(normalize-space(.),"${groupName.trim()}")]//ancestor::a//button[contains(text(),"View")]`, { timeout: 10000 } ) - .should("be.visible") + .should("be.visible").first() .click(); cy.xpath( `//div[contains(@class,"flex items-center justify-between")][.//h3[contains(.,"${expenseName.trim()}")]]//button[contains(., "Detail")]` ) - .should("be.visible") + .should("be.visible").first() .click(); cy.xpath( `//div[contains(@class,"flex items-center justify-between")]//button[contains(., "Pay")]` ) - .should("be.visible") + .should("be.visible").first() .click(); cy.contains("Pending Payment Exists").should("be.visible"); }); Cypress.Commands.add("conFirmExpensePay", (groupName, expenseName, participants) => { + // 1. เข้าหน้า Group และกด View cy.visit(WebPageLocators.groupsPage); - cy.xpath(`//h2[contains(normalize-space(.),"${groupName.trim()}")]//ancestor::a//button[contains(text(),"View")]`, { timeout: 10000 }) - .should('be.visible') + cy.xpath(`//h2[contains(normalize-space(.),"${groupName.trim()}")]/ancestor::a//button[contains(text(),"View")]`, { timeout: 10000 }) + .should('be.visible').first() .click(); + + // 2. กด Detail ของ Expense นั้น cy.xpath( - `//div[contains(@class,"flex items-center justify-between")][.//h3[contains(.,"${expenseName.trim()}")]]//button[contains(., "Detail")]` + `//div[contains(@class,"justify-between")][.//h3[contains(.,"${expenseName.trim()}")]]//button[contains(., "Detail")]` ) - .should("be.visible") + .should("be.visible").first() .click(); + + // 3. วนลูปกด Verify ให้แต่ละคน for (const name of participants) { - cy.log(`🔍 Verifying for ${name}`); + const cleanName = name.trim(); + cy.log(`🔍 Verifying for ${cleanName}`); - cy.xpath(`//div[contains(@class,"flex items-center justify-between")][.//p[@class="font-semibold" and normalize-space(text())="${name}"]]//button[contains(., "Verify")]`, { timeout: 10000 }) - .should("be.visible") - .click(); + // --- 🔥 จุดที่แก้ไข: แก้ XPath ให้หาจาก Text ชื่อคน โดยไม่ยึดติดกับ Class มากเกินไป --- + cy.xpath( + `//div[contains(@class, "justify-between")][.//p[contains(text(), "${cleanName}")]]//button[contains(text(), "Verify")]`, + { timeout: 10000 } + ) + .should("be.visible") + .click(); + + // กด Accept (ใน Modal หรือ Popup) cy.xpath('//button[normalize-space()="Accept"]', { timeout: 5000 }) .should("be.visible") .click(); - cy.wait(500); + + cy.wait(500); // รอ Animation นิดหน่อย } + + // --------------------------------------------------------- + // ส่วนด้านล่างนี้คือการกลับเข้ามาหน้าเดิมเพื่อกด Done (ตาม Logic เดิมของคุณ) + // ถ้า Flow จริงๆ ไม่ต้อง Refresh หน้า ก็สามารถลบส่วนนี้ออกได้ + // --------------------------------------------------------- + cy.visit(WebPageLocators.groupsPage); - cy.xpath(`//h2[contains(normalize-space(.),"${groupName.trim()}")]//ancestor::a//button[contains(text(),"View")]`, { timeout: 10000 }) - .should('be.visible') + cy.xpath(`//h2[contains(normalize-space(.),"${groupName.trim()}")]/ancestor::a//button[contains(text(),"View")]`, { timeout: 10000 }) + .should('be.visible').first() .click(); + cy.xpath( - `//div[contains(@class,"flex items-center justify-between")][.//h3[contains(.,"${expenseName.trim()}")]]//button[contains(., "Detail")]` + `//div[contains(@class,"justify-between")][.//h3[contains(.,"${expenseName.trim()}")]]//button[contains(., "Detail")]` ) - .should("be.visible") + .should("be.visible").first() .click(); for (const name of participants) { - cy.log(`🔍 Verifying for ${name}`); - cy.xpath(`//div[contains(@class,"flex items-center justify-between")][.//p[@class="font-semibold" and normalize-space(text())="${name}"]]//button[contains(., "Done")]`, { timeout: 10000 }) - .should("be.visible") - .click(); + const cleanName = name.trim(); + cy.log(`🔍 Marking Done for ${cleanName}`); + + // ใช้ Logic เดียวกันกับ Verify คือหาจากกล่องที่มีชื่อคนนั้น + cy.xpath( + `//div[contains(@class, "justify-between")][.//p[contains(text(), "${cleanName}")]]//button[contains(text(), "Done")]`, + { timeout: 10000 } + ) + .should("be.visible") + .click(); + } +}); + + +Cypress.Commands.add("createNewGroupAnotheruser", (groupName, paticipant,another) => { + cy.visit(WebPageLocators.groupsPage); + cy.xpath(GroupAddLocator.groupAddButton).click(); + cy.xpath(GroupAddLocator.groupNameInputBox).type(groupName); + cy.get('input[type="file"]').attachFile("banyai_dreamworld.jpg"); + cy.wait(1000); + for (let i = 0; i < paticipant.length; i++) { + cy.xpath(GroupAddLocator.participantInputBox).type(paticipant[i]); + cy.wait(1000); + cy.xpath(`//ul//li[normalize-space(text())="${paticipant[i]}"]`).click(); + cy.wait(1000); } + cy.xpath(GroupAddLocator.praticipantAnotherInput).type(another); + cy.xpath(GroupAddLocator.praticipantAnotherClick).click(); + cy.xpath(GroupAddLocator.createGroupButton).click(); + cy.xpath(GroupAddLocator.groupNameHeader).should("contain.text", groupName); + cy.xpath('//*[@id="root"]/div/nav[2]/div/button[2]').click(); }); + +Cypress.Commands.add("InvalidInput", (groupName, paticipant) => { + cy.visit(WebPageLocators.groupsPage); + cy.xpath(GroupAddLocator.groupAddButton).click(); + cy.xpath(GroupAddLocator.groupNameInputBox).type(groupName); + cy.get('input[type="file"]').attachFile("banyai_dreamworld.jpg"); + cy.wait(1000); + cy.xpath(GroupAddLocator.participantInputBox).type(paticipant); + cy.wait(5000); + cy.xpath(`//*[@id="root"]/div/div/div/div/div/div[3]/div/ul/li[1]`).click(); + cy.wait(5000); +}); + +Cypress.Commands.add("checkTotalbill", (groupName, expenseName) => { + cy.visit(WebPageLocators.groupsPage); + cy.xpath( + `//h2[contains(normalize-space(.),"${groupName.trim()}")]//ancestor::a//button[contains(text(),"View")]`, + { timeout: 10000 } + ) + .should("be.visible").first() + .click(); + + cy.xpath( + `//div[contains(@class,"flex items-center justify-between")][.//h3[contains(.,"${expenseName.trim()}")]]//button[contains(., "Detail")]` + ) + .should("be.visible").first() + .click(); + cy.xpath('//*[@id="root"]/div/div/div[2]/div[2]/div[2]/div').should( + "contain.text", + "Transactions Left" + ) +}); + +Cypress.Commands.add( + "addcurrencyUSD", + (groupName, expenseName, totalAmount, except) => { + cy.visit(WebPageLocators.groupsPage); + cy.xpath( + `//h2[text()="${groupName}"]/ancestor::a//button[contains(text(),"View")]` + ).click(); + cy.xpath(ExpenseLocator.expenseAddButton).click(); + cy.xpath(ExpenseLocator.equalSplit).click(); + cy.xpath(EquallySplitLocator.ExpenseNameInputBox).type(expenseName); + cy.xpath(EquallySplitLocator.TotalAmountInputBox).type(totalAmount); + cy.xpath('//*[@id="root"]/div/div/div[2]/div/button').click(); + cy.xpath('//*[@id="root"]/div/div/div[2]/div/div/label[2]').click(); + cy.xpath('//*[@id="root"]/div/div/div[4]/button/span[1]').click(); + cy.wait(5000); + cy.xpath('//*[@id="root"]/div/div/button[2]').click(); + } +); + +Cypress.Commands.add( + "setExchageRate", + (groupName, expenseName, totalAmount,rate) => { + cy.visit(WebPageLocators.groupsPage); + cy.xpath( + `//h2[text()="${groupName}"]/ancestor::a//button[contains(text(),"View")]` + ).click(); + cy.xpath(ExpenseLocator.expenseAddButton).click(); + cy.xpath(ExpenseLocator.equalSplit).click(); + cy.xpath(EquallySplitLocator.ExpenseNameInputBox).type(expenseName); + cy.xpath(EquallySplitLocator.TotalAmountInputBox).type(totalAmount); + cy.xpath('//*[@id="root"]/div/div/div[2]/div/button').click(); + cy.xpath('//*[@id="root"]/div/div/div[2]/div/div/label[2]').click(); + cy.xpath('//*[@id="root"]/div/div/div[4]/button/span[1]').click(); + cy.xpath('//*[@id="root"]/div/div/div[3]/label/span').click(); + cy.xpath('//*[@id="root"]/div/div/div[4]/div[1]/input').type(rate); + cy.wait(5000); + cy.xpath('//*[@id="root"]/div/div/button[2]').click(); + } +); + +Cypress.Commands.add( + "addAnothercurrency", + (groupName, expenseName, totalAmount, currency) => { + cy.visit(WebPageLocators.groupsPage); + cy.xpath( + `//h2[text()="${groupName}"]/ancestor::a//button[contains(text(),"View")]` + ).click(); + cy.xpath(ExpenseLocator.expenseAddButton).click(); + cy.xpath(ExpenseLocator.equalSplit).click(); + cy.xpath(EquallySplitLocator.ExpenseNameInputBox).type(expenseName); + cy.xpath(EquallySplitLocator.TotalAmountInputBox).type(totalAmount); + cy.xpath('//*[@id="root"]/div/div/div[2]/div/button').click(); + cy.xpath('//*[@id="root"]/div/div/div[2]/div/div/label[4]').click(); + cy.xpath('//*[@id="root"]/div/div/div[2]/input').type(currency); + cy.xpath('//*[@id="root"]/div/div/div[4]/button/span[1]').click(); + cy.wait(5000); + cy.xpath('//*[@id="root"]/div/div/button[2]').click(); + } +); \ No newline at end of file diff --git a/frontend/src/pages/EqualSplitPage.tsx b/frontend/src/pages/EqualSplitPage.tsx index f28f02c..620ea2c 100644 --- a/frontend/src/pages/EqualSplitPage.tsx +++ b/frontend/src/pages/EqualSplitPage.tsx @@ -192,20 +192,23 @@ export default function EqualSplitPage() { let rateNum: number | undefined = undefined; if (activeCurrency !== "THB") { - if (!showExchangeRateInput) { - alert("กรุณาติ๊ก 'Set Exchange Rate' เพื่อกำหนดอัตราแลกเปลี่ยน"); - return; - } + // if (!showExchangeRateInput) { + // alert("กรุณาติ๊ก 'Set Exchange Rate' เพื่อกำหนดอัตราแลกเปลี่ยน"); + // return; + // } if (currency === "CUSTOM" && !activeCurrency) { alert("กรุณาระบุรหัสสกุลเงิน (e.g., EUR)"); return; } - rateNum = Number(exchangeRate); + if(showExchangeRateInput){ + rateNum = Number(exchangeRate); if (!Number.isFinite(rateNum) || rateNum <= 0) { alert("กรุณาระบุ Exchange Rate ให้ถูกต้อง (ต้องมากกว่า 0)"); return; } amountInThb = amountNum * rateNum; + } + } const exchangeRatesMap: { [key: string]: number } = { "THB": 1 };