diff --git a/pom.xml b/pom.xml index 4caca2ef..47b30405 100644 --- a/pom.xml +++ b/pom.xml @@ -17,6 +17,7 @@ UTF-8 + https://testendpoint admin@org.com standard@org.com @@ -177,6 +178,19 @@ 5.18.0 test + + + org.seleniumhq.selenium + selenium-java + 4.15.0 + test + + + org.seleniumhq.selenium + selenium-chrome-driver + 4.15.0 + test + @@ -607,6 +621,11 @@ verify !${skip-unit-tests} + + ${test.endpoint} + ${test.user.default} + ${test.password} + **/action/**, **/dao/**, diff --git a/src/main/java/com/salesforce/dataloader/ui/URLUtil.java b/src/main/java/com/salesforce/dataloader/ui/URLUtil.java index 4f4debfb..7bdc8a51 100644 --- a/src/main/java/com/salesforce/dataloader/ui/URLUtil.java +++ b/src/main/java/com/salesforce/dataloader/ui/URLUtil.java @@ -34,7 +34,36 @@ public class URLUtil { private static Logger logger = DLLogManager.getLogger(URLUtil.class); + private static volatile UrlOpener testHook = null; + + /** + * Set a test hook for URL opening (used by tests to inject Selenium). + * @param opener The UrlOpener implementation to use for tests + */ + public static void setTestHook(UrlOpener opener) { + testHook = opener; + } + + /** + * Clear the test hook to restore normal URL opening behavior. + */ + public static void clearTestHook() { + testHook = null; + } + public static void openURL(String url) { + // Check for test hook first + if (testHook != null) { + try { + logger.debug("Using test hook for URL opening: " + url); + testHook.open(url); + return; + } catch (Exception e) { + logger.warn("Test hook failed, falling back to default behavior: " + e.getMessage()); + } + } + + // Default production behavior if (Desktop.isDesktopSupported()) { Desktop desktop = Desktop.getDesktop(); try { diff --git a/src/main/java/com/salesforce/dataloader/ui/UrlOpener.java b/src/main/java/com/salesforce/dataloader/ui/UrlOpener.java new file mode 100644 index 00000000..93df14a6 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/UrlOpener.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +/** + * Interface for opening URLs, allowing test seams for browser automation. + * Production uses Desktop.browse(), tests can inject Selenium WebDriver. + */ +public interface UrlOpener { + /** + * Opens the specified URL. + * @param url The URL to open + * @throws Exception if the URL cannot be opened + */ + void open(String url) throws Exception; +} \ No newline at end of file diff --git a/src/test/java/com/salesforce/dataloader/oauth/OAuthTestSeamSeleniumTest.java b/src/test/java/com/salesforce/dataloader/oauth/OAuthTestSeamSeleniumTest.java new file mode 100644 index 00000000..de82a4f7 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/oauth/OAuthTestSeamSeleniumTest.java @@ -0,0 +1,457 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.oauth; + +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.ui.URLUtil; +import com.salesforce.dataloader.ui.SeleniumUrlOpener; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.openqa.selenium.By; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.chrome.ChromeDriver; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.support.ui.WebDriverWait; +import org.openqa.selenium.support.ui.ExpectedConditions; +import org.openqa.selenium.TimeoutException; + +import java.time.Duration; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import org.junit.Assert; + +/** + * Clean OAuth test that uses the test seam pattern to inject Selenium into handleOAuthLogin(). + * Tests the complete OAuth flow: PKCE timeout -> Device Flow -> Login -> Allow -> Continue + */ +public class OAuthTestSeamSeleniumTest extends ConfigTestBase { + + private WebDriver driver; + private WebDriverWait wait; + + @Before + public void setUp() throws Exception { + super.setupController(); + + System.out.println("๐Ÿ”ง Setting up OAuth test with Selenium..."); + + // Setup Selenium + ChromeOptions options = new ChromeOptions(); + options.addArguments("--disable-web-security"); + options.addArguments("--disable-features=VizDisplayCompositor"); + options.addArguments("--no-sandbox"); + + driver = new ChromeDriver(options); + wait = new WebDriverWait(driver, Duration.ofSeconds(30)); + + // Inject Selenium as the URL opener using test seam + SeleniumUrlOpener seleniumOpener = new SeleniumUrlOpener(driver, false); + URLUtil.setTestHook(seleniumOpener); + System.out.println("โœ… Test seam configured - OAuth will use Selenium browser"); + } + + @After + public void tearDown() throws Exception { + try { + URLUtil.clearTestHook(); + + // Clear OAuth tokens + AppConfig config = getController().getAppConfig(); + if (config != null) { + config.setValue(AppConfig.PROP_OAUTH_ACCESSTOKEN, ""); + config.setValue(AppConfig.PROP_OAUTH_REFRESHTOKEN, ""); + config.setValue(AppConfig.PROP_OAUTH_INSTANCE_URL, ""); + } + + if (getController() != null) { + getController().logout(); + } + + if (driver != null) { + Thread.sleep(2000); + driver.quit(); + } + + } catch (Exception e) { + System.err.println("โš ๏ธ Warning during teardown: " + e.getMessage()); + } + } + + @Test + public void testHandleOAuthLoginWithPKCEFlow() throws Exception { + System.out.println("๐Ÿงช Testing handleOAuthLogin() with automated Selenium flow"); + + // Start handleOAuthLogin() in background + CompletableFuture handleOAuthFuture = CompletableFuture.supplyAsync(() -> { + try { + System.out.println("๐Ÿš€ Starting handleOAuthLogin()..."); + + OAuthFlowHandler oauthHandler = new OAuthFlowHandler( + getController().getAppConfig(), + (status) -> System.out.println("OAuth: " + status), + null, // null controller to avoid SWT issues (we are not testing dataloader ui layer; only oauth flow in browser) + null // null runnable to avoid SWT issues (we are not testing dataloader ui layer; only oauth flow in browser) + ); + + boolean result = oauthHandler.handleOAuthLogin(); + System.out.println("๐ŸŽฏ handleOAuthLogin() result: " + result); + return result; + + } catch (Exception e) { + System.err.println("โŒ handleOAuthLogin() failed: " + e.getMessage()); + return false; + } + }); + + System.out.println("โณ Waiting for OAuth flow to navigate browser..."); + Thread.sleep(3000); + + // Handle the OAuth flow with Selenium + try { + String currentUrl = driver.getCurrentUrl(); + System.out.println("๐ŸŒ Current URL: " + currentUrl); + + if (currentUrl.contains("salesforce") || currentUrl.contains("orgfarm")) { + System.out.println("โœ… Selenium navigated to OAuth URL"); + + // Handle login if needed + String pageSource = driver.getPageSource(); + if (pageSource.contains("name=\"username\"") || pageSource.contains("name=\"pw\"")) { + System.out.println("๐Ÿ” Performing login..."); + performAutomatedLogin(); + Thread.sleep(2000); + + // Assert we're no longer on login page + String postLoginUrl = driver.getCurrentUrl(); + assertFalse("Should not be on login page after login", + postLoginUrl.contains("/login")); + } + + // Handle authorization if needed + pageSource = driver.getPageSource(); + if (pageSource.contains("Allow") || pageSource.contains("Authorize")) { + System.out.println("๐Ÿ–ฑ๏ธ Clicking authorization..."); + handleAuthorizationPage(); + Thread.sleep(3000); + + // Assert we moved past authorization page + String postAuthUrl = driver.getCurrentUrl(); + assertTrue("Should be redirected after authorization", + !postAuthUrl.equals(currentUrl)); + } + + currentUrl = driver.getCurrentUrl(); + if (currentUrl.contains("localhost:")) { + System.out.println("๐ŸŽ‰ OAuth callback completed"); + + // Assert we're on localhost callback + assertTrue("Should be on localhost callback URL", + currentUrl.contains("localhost:")); + + // Use WebDriverWait to handle page loading and success verification + try { + WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5)); + wait.until(ExpectedConditions.or( + ExpectedConditions.titleContains("Success"), + ExpectedConditions.urlContains("success"), + ExpectedConditions.presenceOfElementLocated(By.xpath("//*[contains(text(), 'Authorization Successful')]")), + ExpectedConditions.presenceOfElementLocated(By.xpath("//*[contains(text(), 'authorization successful')]")), + ExpectedConditions.presenceOfElementLocated(By.xpath("//*[contains(text(), 'SUCCESS')]")) + )); + System.out.println("โœ… Authorization success page verified"); + } catch (org.openqa.selenium.TimeoutException e) { + System.out.println("โš ๏ธ Success page elements not found within timeout, checking page source..."); + try { + String pageContent = driver.getPageSource(); + if (pageContent.contains("Authorization Successful!") || + pageContent.contains("authorization successful") || + pageContent.contains("SUCCESS")) { + System.out.println("โœ… Authorization success verified in page source"); + } else { + System.out.println("โš ๏ธ Authorization success message not found, but OAuth tokens obtained successfully"); + } + } catch (Exception pageSourceException) { + System.out.println("โš ๏ธ Could not verify success page, but OAuth flow completed successfully"); + } + } + } + + } else { + Assert.fail("OAuth URL not detected: " + currentUrl); + } + + } catch (Exception e) { + Assert.fail("Selenium error during OAuth flow: " + e.getMessage()); + } + + // Wait for handleOAuthLogin() to complete + try { + boolean result = handleOAuthFuture.get(30, TimeUnit.SECONDS); + + if (result) { + System.out.println("๐ŸŽ‰ SUCCESS: OAuth login completed"); + + // Verify tokens were set + AppConfig config = getController().getAppConfig(); + String accessToken = config.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN); + String instanceUrl = config.getString(AppConfig.PROP_OAUTH_INSTANCE_URL); + + System.out.println("๐Ÿ“‹ Tokens: " + + (accessToken != null && !accessToken.isEmpty() ? "โœ… Access " : "โŒ Access ") + + (instanceUrl != null && !instanceUrl.isEmpty() ? "โœ… Instance" : "โŒ Instance")); + + assertTrue("handleOAuthLogin() should return true", result); + assertNotNull("Access token should be set", accessToken); + assertFalse("Access token should not be empty", accessToken.trim().isEmpty()); + + } else { + Assert.fail("handleOAuthLogin() returned false - OAuth flow failed"); + } + + } catch (Exception e) { + Assert.fail("handleOAuthLogin() timed out or failed: " + e.getMessage()); + } + + System.out.println("โœ… Test completed"); + } + + /** + * Clean OAuth flow test: PKCE timeout -> Device Flow -> Login -> Allow -> Continue + * Refactored based on actual execution path analysis. + */ + @Test + public void testHandleOAuthLoginWithDeviceFlow() throws Exception { + System.out.println("๐Ÿงช Starting clean OAuth flow test..."); + CompletableFuture oauthFuture = CompletableFuture.supplyAsync(() -> { + try { + OAuthFlowHandler oauthHandler = new OAuthFlowHandler( + getController().getAppConfig(), + (status) -> System.out.println("OAuth: " + status), + null, // null controller to avoid SWT issues (we are not testing dataloader ui layer; only oauth flow in browser) + null // null runnable to avoid SWT issues (we are not testing dataloader ui layer; only oauth flow in browser) + ); + return oauthHandler.handleOAuthLogin(); + } catch (Exception e) { + System.err.println("OAuth flow error: " + e.getMessage()); + return false; + } + }); + + // Step 1: Wait for OAuth flow to determine and navigate to Device Flow + System.out.println("โณ Step 1: Waiting for OAuth pre-flight checks and Device Flow navigation..."); + Thread.sleep(3000); + + // Verify we're on Device Flow page + String currentUrl = driver.getCurrentUrl(); + assertTrue("Should be on Device Flow page", currentUrl.contains("setup/connect")); + System.out.println("โœ… Step 1 verified: On Device Flow page"); + + // Verify the page contains expected elements + String pageSource = driver.getPageSource(); + assertTrue("Device Flow page should contain Connect button", + pageSource.contains("Connect") || pageSource.contains("Submit")); + + // Step 2: Click Connect button (code is pre-filled) + System.out.println("๐Ÿ“ฑ Step 2: Clicking Connect button..."); + WebElement connectButton = driver.findElement(By.xpath("//input[@type='submit' and (@value='Connect' or @value='Submit')]")); + assertNotNull("Connect button should be found", connectButton); + assertTrue("Connect button should be enabled", connectButton.isEnabled()); + connectButton.click(); + Thread.sleep(2000); + + // Verify we were redirected from Device Flow page + String postConnectUrl = driver.getCurrentUrl(); + assertNotEquals("URL should change after clicking Connect", currentUrl, postConnectUrl); + System.out.println("โœ… Step 2 verified: Redirected after Connect click"); + + // Step 3: Perform login (redirected to login page) + System.out.println("๐Ÿ” Step 3: Performing login..."); + + // Verify we're on login page + String loginPageSource = driver.getPageSource(); + assertTrue("Should be on login page", + loginPageSource.contains("name=\"username\"") && loginPageSource.contains("name=\"pw\"")); + + performAutomatedLogin(); + + // Verify login was successful (no longer on login page) + String postLoginUrl = driver.getCurrentUrl(); + assertFalse("Should not be on login page after successful login", + postLoginUrl.contains("/login")); + System.out.println("โœ… Step 3 verified: Login successful"); + + // Step 4: Click Allow button on authorization page + System.out.println("โœ… Step 4: Clicking Allow button..."); + Thread.sleep(2000); // Wait for authorization page to load + + // Verify we're on authorization page + String authPageSource = driver.getPageSource(); + assertTrue("Should be on authorization page with Allow button", + authPageSource.contains("Allow")); + + WebElement allowButton = driver.findElement(By.xpath("//input[normalize-space(@value)='Allow']")); + assertNotNull("Allow button should be found", allowButton); + assertTrue("Allow button should be enabled", allowButton.isEnabled()); + allowButton.click(); + Thread.sleep(3000); + + // Verify we reached success page + String successUrl = driver.getCurrentUrl(); + assertTrue("Should reach success page after Allow", successUrl.contains("user_approved=1")); + System.out.println("โœ… Step 4 verified: Authorization successful"); + + // Step 5: Click Continue button to complete flow + System.out.println("โžก๏ธ Step 5: Clicking Continue button..."); + + // Verify Continue button exists + String successPageSource = driver.getPageSource(); + assertTrue("Success page should contain Continue button", + successPageSource.contains("Continue")); + + WebElement continueButton = driver.findElement(By.xpath("//input[@value='Continue']")); + assertNotNull("Continue button should be found", continueButton); + assertTrue("Continue button should be enabled", continueButton.isEnabled()); + continueButton.click(); + + System.out.println("๐ŸŽ‰ Clean OAuth flow test PASSED!"); + URLUtil.clearTestHook(); + } + + /** + * Test to verify that DataLoader fails when PKCE is disabled and only device flow is enabled. + * This represents the scenario before 7 AM on 9/2 when Connected Apps only support device flow. + * DL version 64.0.1 only supports PKCE, so this should fail gracefully. + */ + @Test + public void testHandleOAuthLoginWithPKCEDisabled() throws Exception { + System.out.println("๐Ÿงช Testing DataLoader behavior when PKCE is disabled (device flow only)..."); + + // Start OAuth flow in background + CompletableFuture oauthFuture = CompletableFuture.supplyAsync(() -> { + try { + OAuthFlowHandler oauthHandler = new OAuthFlowHandler( + getController().getAppConfig(), + (status) -> System.out.println("OAuth Status: " + status), + null, // null controller to avoid SWT issues + null // null runnable to avoid SWT issues + ); + return oauthHandler.handleOAuthLogin(); + } catch (Exception e) { + return false; + } + }); + + // Wait for OAuth flow to complete (timeout is 60 seconds) + System.out.println("โณ Waiting for OAuth flow to complete..."); + Boolean oauthResult; + try { + oauthResult = oauthFuture.get(65, TimeUnit.SECONDS); + } catch (Exception e) { + oauthResult = false; + } + + // Analyze the OAuth attempt + String currentUrl = driver.getCurrentUrl(); + System.out.println("๐Ÿ” OAuth result: " + oauthResult + ", URL: " + currentUrl); + + // Check what type of OAuth flow was attempted + boolean pkceAttempted = currentUrl.contains("oauth2/authorize") && + (currentUrl.contains("code_challenge") || currentUrl.contains("error=")); + boolean deviceFlowAttempted = currentUrl.contains("setup/connect"); + + // Main assertions + assertTrue("PKCE authorization should have been attempted", pkceAttempted); + assertFalse("Device flow should NOT have been attempted", deviceFlowAttempted); + assertFalse("OAuth should fail when PKCE is disabled", oauthResult); + + System.out.println("โœ… TEST PASSED: DataLoader attempted PKCE (not device flow) and failed as expected"); + } + + /** + * Handle authorization page by clicking Allow/Authorize button + */ + private void handleAuthorizationPage() throws Exception { + try { + WebElement allowButton = wait.until(ExpectedConditions.elementToBeClickable( + By.xpath("//*[contains(text(), 'Allow') or contains(text(), 'Authorize')]"))); + allowButton.click(); + } catch (Exception e) { + System.out.println("โš ๏ธ Authorization button not found: " + e.getMessage()); + } + } + + /** + * Perform automated login using credentials from pom.xml system properties + */ + private void performAutomatedLogin() throws Exception { + String username = System.getProperty("test.user.default"); + String password = System.getProperty("test.password"); + + // Assert credentials are available + assertNotNull("Username should be provided via system property", username); + assertNotNull("Password should be provided via system property", password); + assertFalse("Username should not be empty", username.trim().isEmpty()); + assertFalse("Password should not be empty", password.trim().isEmpty()); + + System.out.println("๐Ÿ“‹ Using credentials from pom.xml: " + username); + + try { + WebElement usernameField = wait.until(ExpectedConditions.presenceOfElementLocated(By.name("username"))); + assertNotNull("Username field should be found", usernameField); + assertTrue("Username field should be enabled", usernameField.isEnabled()); + usernameField.clear(); + usernameField.sendKeys(username); + + WebElement passwordField = driver.findElement(By.name("pw")); + assertNotNull("Password field should be found", passwordField); + assertTrue("Password field should be enabled", passwordField.isEnabled()); + passwordField.clear(); + passwordField.sendKeys(password); + + WebElement loginButton = driver.findElement(By.name("Login")); + assertNotNull("Login button should be found", loginButton); + assertTrue("Login button should be enabled", loginButton.isEnabled()); + loginButton.click(); + + // Wait for login to complete (no longer on login page) + wait.until(ExpectedConditions.not(ExpectedConditions.urlContains("/login"))); + + // Verify we're no longer on login page + String currentUrl = driver.getCurrentUrl(); + assertFalse("Should not be on login page after successful login", + currentUrl.contains("/login")); + + } catch (Exception e) { + Assert.fail("Login failed: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/test/java/com/salesforce/dataloader/ui/SeleniumUrlOpener.java b/src/test/java/com/salesforce/dataloader/ui/SeleniumUrlOpener.java new file mode 100644 index 00000000..9a770bdf --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/ui/SeleniumUrlOpener.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.WebDriver; + +/** + * Test implementation of UrlOpener that uses Selenium WebDriver. + * This allows tests to control browser navigation instead of opening system browser. + */ +public final class SeleniumUrlOpener implements UrlOpener { + private final WebDriver driver; + private final boolean openInNewTab; + + public SeleniumUrlOpener(WebDriver driver) { + this(driver, false); + } + + public SeleniumUrlOpener(WebDriver driver, boolean openInNewTab) { + this.driver = driver; + this.openInNewTab = openInNewTab; + } + + @Override + public void open(String url) throws Exception { + System.out.println("๐ŸŽฏ SeleniumUrlOpener: Navigating to " + url); + + if (openInNewTab) { + // Open in new tab and switch to it + ((JavascriptExecutor) driver).executeScript("window.open(arguments[0], '_blank');", url); + + // Switch to the new tab + String originalWindow = driver.getWindowHandle(); + for (String windowHandle : driver.getWindowHandles()) { + if (!windowHandle.equals(originalWindow)) { + driver.switchTo().window(windowHandle); + break; + } + } + System.out.println("๐ŸŽฏ SeleniumUrlOpener: Opened in new tab and switched"); + } else { + // Open in current tab + driver.get(url); + System.out.println("๐ŸŽฏ SeleniumUrlOpener: Opened in current tab"); + } + } +} \ No newline at end of file diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties index d894aaa1..898f1616 100644 --- a/src/test/resources/test.properties +++ b/src/test/resources/test.properties @@ -24,6 +24,19 @@ main.src.dir=${project.build.sourceDirectory} target.dir=${project.build.directory} process.encryptionKeyFile=${test.encryptionFile} + +## OAuth client IDs for testing +## For OAuth rollout testing, uncomment and modify manually or via script and input consumer key for connected app - +#sfdc.oauth.Production.bulk.clientid= +#sfdc.oauth.Production.partner.clientid= + +## Bulk API configuration - controls which Connected App is used +## DataLoaderPartnerUI (default): sfdc.useBulkApi=false, sfdc.useBulkV2Api=false +## DataLoaderBulkUI: sfdc.useBulkApi=true OR sfdc.useBulkV2Api=true +## Uncomment and modify via script for testing - +#sfdc.useBulkApi= +#sfdc.useBulkV2Api= + ## TODO: properties below here don't really belong and should be removed ## defaults that don't necessarily belong here test.account.extid=Oracle_Id__c