UNPKG

sf-agent-framework

Version:

AI Agent Orchestration Framework for Salesforce Development - Two-phase architecture with 70% context reduction

554 lines (429 loc) 16.3 kB
# UI Test Automation Task ## Task Overview **Objective:** Create automated UI tests for Salesforce Lightning Experience, Classic, and Communities using appropriate testing frameworks. **When to Use:** - Validating Lightning Web Components functionality - Testing end-to-end user workflows - Regression testing for UI changes - Cross-browser compatibility testing - Mobile responsiveness validation - Accessibility compliance testing ## Prerequisites - Test scenarios documented - Test data prepared - Test environment access configured - Testing framework selected (Jest, Selenium, Provar, etc.) - Browser drivers installed ## Task Steps ### Step 1: Setup Testing Framework #### For Lightning Web Components (Jest) ```bash # Install Jest and dependencies npm install --save-dev @salesforce/sfdx-lwc-jest jest # Configure jest.config.js module.exports = { preset: '@salesforce/sfdx-lwc-jest', moduleNameMapper: { '^lightning/(.*)$': '<rootDir>/force-app/test/jest-mocks/lightning/$1/$1', '^@salesforce/(.*)$': '<rootDir>/force-app/test/jest-mocks/@salesforce/$1/$1' }, testPathIgnorePatterns: [ '<rootDir>/node_modules/', '<rootDir>/test/jest-mocks/' ] }; ``` #### For End-to-End Testing (Selenium) ```javascript // Install Selenium WebDriver npm install --save-dev selenium-webdriver chromedriver // Basic setup const {Builder, By, Key, until} = require('selenium-webdriver'); const chrome = require('selenium-webdriver/chrome'); const options = new chrome.Options(); options.addArguments('--disable-gpu'); options.addArguments('--no-sandbox'); ``` ### Step 2: Create Page Object Model ```javascript // LoginPage.js class LoginPage { constructor(driver) { this.driver = driver; this.url = 'https://login.salesforce.com'; // Element locators this.usernameInput = By.id('username'); this.passwordInput = By.id('password'); this.loginButton = By.id('Login'); this.rememberMeCheckbox = By.id('rememberUn'); } async navigate() { await this.driver.get(this.url); } async login(username, password) { await this.driver.findElement(this.usernameInput).sendKeys(username); await this.driver.findElement(this.passwordInput).sendKeys(password); await this.driver.findElement(this.loginButton).click(); // Wait for navigation await this.driver.wait(until.urlContains('lightning'), 10000); } } // AccountPage.js class AccountPage { constructor(driver) { this.driver = driver; // Element locators this.newButton = By.xpath("//a[@title='New']"); this.accountNameInput = By.xpath("//input[@name='Name']"); this.saveButton = By.xpath("//button[@name='SaveEdit']"); this.toastMessage = By.xpath("//span[@class='toastMessage']"); } async createAccount(accountName) { await this.driver.findElement(this.newButton).click(); await this.driver.wait(until.elementLocated(this.accountNameInput), 5000); await this.driver.findElement(this.accountNameInput).sendKeys(accountName); await this.driver.findElement(this.saveButton).click(); // Wait for success message await this.driver.wait(until.elementLocated(this.toastMessage), 5000); } async getToastMessage() { const toast = await this.driver.findElement(this.toastMessage); return await toast.getText(); } } ``` ### Step 3: Implement LWC Component Tests ```javascript // accountList.test.js import { createElement } from 'lwc'; import AccountList from 'c/accountList'; import getAccounts from '@salesforce/apex/AccountController.getAccounts'; // Mock Apex method jest.mock( '@salesforce/apex/AccountController.getAccounts', () => { return { default: jest.fn(), }; }, { virtual: true } ); describe('c-account-list', () => { afterEach(() => { while (document.body.firstChild) { document.body.removeChild(document.body.firstChild); } jest.clearAllMocks(); }); test('displays accounts when data returned', async () => { // Arrange const mockAccounts = [ { Id: '001', Name: 'Test Account 1', Industry: 'Technology' }, { Id: '002', Name: 'Test Account 2', Industry: 'Finance' }, ]; getAccounts.mockResolvedValue(mockAccounts); // Act const element = createElement('c-account-list', { is: AccountList, }); document.body.appendChild(element); // Wait for async operations await Promise.resolve(); // Assert const accountElements = element.shadowRoot.querySelectorAll('.account-tile'); expect(accountElements.length).toBe(2); expect(accountElements[0].textContent).toContain('Test Account 1'); }); test('displays error when Apex method fails', async () => { // Arrange const mockError = new Error('Failed to fetch accounts'); getAccounts.mockRejectedValue(mockError); // Act const element = createElement('c-account-list', { is: AccountList, }); document.body.appendChild(element); await Promise.resolve(); // Assert const errorElement = element.shadowRoot.querySelector('.error-message'); expect(errorElement).toBeTruthy(); expect(errorElement.textContent).toContain('Failed to fetch accounts'); }); test('handles account selection', async () => { // Arrange const mockAccounts = [{ Id: '001', Name: 'Test Account 1' }]; getAccounts.mockResolvedValue(mockAccounts); const element = createElement('c-account-list', { is: AccountList, }); // Mock event handler const handler = jest.fn(); element.addEventListener('accountselected', handler); document.body.appendChild(element); await Promise.resolve(); // Act const accountTile = element.shadowRoot.querySelector('.account-tile'); accountTile.click(); // Assert expect(handler).toHaveBeenCalled(); expect(handler.mock.calls[0][0].detail.accountId).toBe('001'); }); }); ``` ### Step 4: Create End-to-End Tests ```javascript // e2e/accountCreation.test.js const { Builder, By, until } = require('selenium-webdriver'); const LoginPage = require('./pages/LoginPage'); const AccountPage = require('./pages/AccountPage'); const assert = require('assert'); describe('Account Creation E2E Test', function () { let driver; let loginPage; let accountPage; before(async function () { driver = await new Builder().forBrowser('chrome').build(); loginPage = new LoginPage(driver); accountPage = new AccountPage(driver); }); after(async function () { await driver.quit(); }); it('should create a new account successfully', async function () { // Login await loginPage.navigate(); await loginPage.login(process.env.SF_USERNAME, process.env.SF_PASSWORD); // Navigate to Accounts await driver.get(process.env.SF_INSTANCE_URL + '/lightning/o/Account/list'); // Create new account const accountName = `Test Account ${Date.now()}`; await accountPage.createAccount(accountName); // Verify success const toastMessage = await accountPage.getToastMessage(); assert(toastMessage.includes('was created'), 'Account creation failed'); }); it('should validate required fields', async function () { // Navigate to new account form await driver.get(process.env.SF_INSTANCE_URL + '/lightning/o/Account/new'); // Try to save without required fields await driver.findElement(By.xpath("//button[@name='SaveEdit']")).click(); // Check for error message const errorMessage = await driver .findElement(By.xpath("//div[contains(@class, 'slds-text-color_error')]")) .getText(); assert(errorMessage.includes('These required fields must be completed'), 'Expected validation error not shown'); }); }); ``` ### Step 5: Implement Mobile Testing ```javascript // mobile/mobileApp.test.js const { Builder, By, until } = require('selenium-webdriver'); describe('Salesforce Mobile App Tests', function () { let driver; before(async function () { // Configure for mobile testing const capabilities = { platformName: 'iOS', platformVersion: '14.0', deviceName: 'iPhone 12', app: 'com.salesforce.chatter', automationName: 'XCUITest', }; driver = await new Builder().usingServer('http://localhost:4723/wd/hub').withCapabilities(capabilities).build(); }); it('should login to mobile app', async function () { // Wait for login screen await driver.wait(until.elementLocated(By.id('username')), 10000); // Enter credentials await driver.findElement(By.id('username')).sendKeys(process.env.SF_USERNAME); await driver.findElement(By.id('password')).sendKeys(process.env.SF_PASSWORD); // Login await driver.findElement(By.id('loginButton')).click(); // Verify home screen await driver.wait(until.elementLocated(By.id('home_tab')), 15000); }); it('should create activity from mobile', async function () { // Navigate to activities await driver.findElement(By.id('activities_tab')).click(); // Create new task await driver.findElement(By.id('new_task_button')).click(); // Fill task details await driver.findElement(By.id('subject')).sendKeys('Mobile Test Task'); await driver.findElement(By.id('save_button')).click(); // Verify creation const successMessage = await driver.findElement(By.id('success_toast')).getText(); assert(successMessage.includes('Task created'), 'Task creation failed'); }); }); ``` ### Step 6: Accessibility Testing ```javascript // accessibility/a11y.test.js const { Builder } = require('selenium-webdriver'); const AxeBuilder = require('@axe-core/webdriverjs'); describe('Accessibility Tests', function () { let driver; before(async function () { driver = await new Builder().forBrowser('chrome').build(); // Login to Salesforce await loginToSalesforce(driver); }); it('should have no accessibility violations on Account page', async function () { await driver.get(process.env.SF_INSTANCE_URL + '/lightning/o/Account/list'); const results = await new AxeBuilder(driver).withTags(['wcag2a', 'wcag2aa']).analyze(); assert.equal(results.violations.length, 0, `Found ${results.violations.length} accessibility violations`); }); it('should have proper ARIA labels', async function () { // Check for ARIA labels on interactive elements const buttons = await driver.findElements(By.css('button')); for (let button of buttons) { const ariaLabel = await button.getAttribute('aria-label'); const text = await button.getText(); assert(ariaLabel || text, 'Button missing accessible label'); } }); it('should support keyboard navigation', async function () { // Test tab navigation await driver.findElement(By.css('body')).sendKeys(Key.TAB); const activeElement = await driver.executeScript('return document.activeElement.tagName'); assert.notEqual(activeElement, 'BODY', 'Tab navigation not working'); }); }); ``` ### Step 7: Performance Testing ```javascript // performance/pageLoad.test.js describe('Performance Tests', function () { it('should load Account list within 3 seconds', async function () { const startTime = Date.now(); await driver.get(process.env.SF_INSTANCE_URL + '/lightning/o/Account/list'); await driver.wait(until.elementLocated(By.css('.slds-table')), 10000); const loadTime = Date.now() - startTime; assert(loadTime < 3000, `Page load took ${loadTime}ms, expected < 3000ms`); // Log performance metrics const perfData = await driver.executeScript('return window.performance.timing'); console.log('Performance Metrics:', { domContentLoaded: perfData.domContentLoadedEventEnd - perfData.navigationStart, loadComplete: perfData.loadEventEnd - perfData.navigationStart, }); }); }); ``` ### Step 8: Cross-Browser Testing ```javascript // crossBrowser/compatibility.test.js const browsers = ['chrome', 'firefox', 'safari', 'edge']; browsers.forEach((browser) => { describe(`${browser} Compatibility Tests`, function () { let driver; before(async function () { driver = await new Builder().forBrowser(browser).build(); }); it('should render Lightning components correctly', async function () { await loginToSalesforce(driver); await driver.get(process.env.SF_INSTANCE_URL + '/lightning/page/home'); // Check key components render const appLauncher = await driver.findElement(By.css('.slds-icon-waffle')); assert(await appLauncher.isDisplayed(), 'App launcher not visible'); // Check Lightning Design System styles applied const primaryButton = await driver.findElement(By.css('.slds-button_brand')); const backgroundColor = await primaryButton.getCssValue('background-color'); assert(backgroundColor, 'SLDS styles not applied'); }); }); }); ``` ### Step 9: Create Test Utilities ```javascript // utils/testHelpers.js class TestHelpers { static async waitForLightning(driver) { await driver.wait(until.elementLocated(By.css('.slds-template_default')), 15000); } static async waitForToast(driver) { const toast = await driver.wait(until.elementLocated(By.css('.slds-notify')), 5000); return await toast.getText(); } static async selectRecordType(driver, recordTypeName) { await driver.findElement(By.xpath(`//span[text()='${recordTypeName}']`)).click(); await driver.findElement(By.xpath("//button[text()='Next']")).click(); } static async fillLookupField(driver, fieldLabel, searchText) { // Click lookup field const lookup = await driver.findElement(By.xpath(`//label[text()='${fieldLabel}']/..//input`)); await lookup.click(); // Search await lookup.sendKeys(searchText); await driver.sleep(1000); // Wait for search // Select first result await driver.findElement(By.css('.slds-listbox__option:first-child')).click(); } } ``` ### Step 10: Configure Test Execution ```json // package.json test scripts { "scripts": { "test:lwc": "jest", "test:e2e": "mocha e2e/**/*.test.js --timeout 30000", "test:mobile": "mocha mobile/**/*.test.js --timeout 60000", "test:a11y": "mocha accessibility/**/*.test.js", "test:performance": "mocha performance/**/*.test.js", "test:all": "npm run test:lwc && npm run test:e2e" } } ``` ## CI/CD Integration ```yaml # .github/workflows/ui-tests.yml name: UI Tests on: [push, pull_request] jobs: ui-tests: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Node.js uses: actions/setup-node@v2 with: node-version: '14' - name: Install dependencies run: npm ci - name: Run LWC tests run: npm run test:lwc - name: Setup Chrome uses: browser-actions/setup-chrome@latest - name: Run E2E tests env: SF_USERNAME: ${{ secrets.SF_USERNAME }} SF_PASSWORD: ${{ secrets.SF_PASSWORD }} SF_INSTANCE_URL: ${{ secrets.SF_INSTANCE_URL }} run: npm run test:e2e - name: Upload test results uses: actions/upload-artifact@v2 with: name: test-results path: test-results/ ``` ## Success Criteria - [ ] All UI components have test coverage - [ ] E2E critical paths tested - [ ] Cross-browser compatibility verified - [ ] Mobile functionality tested - [ ] Accessibility standards met (WCAG 2.1 AA) - [ ] Performance benchmarks achieved - [ ] Tests integrated with CI/CD pipeline ## Common Issues and Solutions | Issue | Solution | | ----------------------- | ------------------------------------------------- | | Element not found | Use explicit waits, verify selectors | | Stale element reference | Re-find element after page updates | | Timeout errors | Increase wait times, check for loading indicators | | Authentication issues | Use connected app, handle MFA | ## References - [LWC Testing](https://developer.salesforce.com/docs/component-library/documentation/en/lwc/testing) - [Selenium Documentation](https://www.selenium.dev/documentation/) - Test plan template: `templates#test-plan-tmpl.md`