I am extremely new the Android app development and Stack Overflow. I am trying to recreate traceroute in an Android app since Android devices do not come with traceroute by default. I've encountered a couple stack overflow posts talking about solutions to this, but I have still run into challenges.
Traceroute on android - the top post on this thread links an Android Studio project that implements traceroute using ping. If I understand the algorithm correctly, it continually pings the destination IP, incrementing the time-to-live field to obtain information about intermediary routers. I've tried to recreate this behavior, but for certain values of TTL, the ping stalls and doesn't retrieve any router information. I'm not really sure why this happens. Here's a quick demo function I spun up... at some point in the loop the pings stall.
public static void smallTracerouteDemoShowingThatTheProgramStallsAtCertainTTLs() {
try {
String host = "google.com";
int maxTTL = 20;
for (int i = 1; i < maxTTL; i++) {
// Create a process that executes the ping command
Process p = Runtime.getRuntime().exec("ping -c 1 -t " + i + " " + host);
// Get a buffered reader with the information returned by the ping
BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
// Convert the BufferedReader to a string
String dataReturnedByPing = "";
for (String line; (line = br.readLine()) != null; dataReturnedByPing += "\n"+line);
// Print out information about each TTL
System.out.println("TTL = " + i + " out of " + maxTTL);
System.out.println(dataReturnedByPing);
System.out.println("========================================");
}
} catch (Exception e) {
e.printStackTrace();
}
}
how to run traceroute command through your application? - The solution on this thread suggests using BusyBox. I've not used BusyBox as yet, but it seems like I would have to embed BusyBox into my app to get things to work. After doing some research it looks like BusyBox provides numerous Linux commands through one executable. I'm a bit hesitant to explore this option because I really only need the traceroute command. In addition, I know that Android targets a few different CPU architectures, and I'm not sure if one executable will support them all.
I've also run into a github repository that takes another approach to running traceroute:
https://github.com/wangjing53406/traceroute-for-android - In this repository the author embeds the traceroute source code into the project and uses the NDK to build the source code along with the rest of his app. I really like this approach because it feels the most "correct." It uses a built traceroute instead of a Java-based implementation, so you can't find yourself in a situation where the Java implementation gives you one thing and the actual traceroute gives you another. When I open this project to experiment with it, my build fails. The top line says:
org.gradle.initialization.ReportedException: org.gradle.internal.exceptions.LocationAwareException: A problem occurred configuring root project 'traceroute-for-android-master'.
Any help on why this happens or ways to troubleshoot it would be fantastic.
For reference, the minimum SDK I am targeting is API 21 and I am running on Android Studio 3.3.0.
So, at this point I'm stumped. If you were trying to make an app that would let you execute traceroute commands, how would you do it? I really like the NDK approach because it guarantees you're getting true traceroute behavior. If you have any guides to getting that set up for my Android version/SDK, I would appreciate if you would post them. If you'd take another approach I'd to hear about it as well.
Thank you in advance.
I'm running the latest version of Selenium WebDriver with Geckodriver. I want to prevent Selenium from creating temporary Firefox Profiles in the temporary files directory when launching a new instance of WebDriver. Instead I want to use the original Firefox Profile directly. This has double benefit. First, it saves time (it takes significant amount of time for the profile to be copied to the temporary directory). Second, it ensures that cookies created during session are saved to the original profile. Before Selenium started relying on Geckodriver I was able to solve this problem by editing the class FirefoxProfile.class in SeleniumHQ as seen below:
public File layoutOnDisk() {
File profileDir;
if (this.disableTempProfileCreation) {
profileDir = this.model;
return profileDir;
} else {
try {
profileDir = TemporaryFilesystem.getDefaultTmpFS().createTempDir("ABC", "XYZ");
File userPrefs = new File(profileDir, "user.js");
this.copyModel(this.model, profileDir);
this.installExtensions(profileDir);
this.deleteLockFiles(profileDir);
this.deleteExtensionsCacheIfItExists(profileDir);
this.updateUserPrefs(userPrefs);
return profileDir;
} catch (IOException var3) {
throw new UnableToCreateProfileException(var3);
}
}
}
This would stop Selenium from creating a temporary Firefox Profile when the parameter disableTempProfileCreation was set to true.
However, now that Selenium is being controlled by Geckodriver this solution no longer works as the creation (and launch) of Firefox Profile is controlled by Geckodriver.exe (which is written in Rust language). How can I achieve the same objective with Geckodriver? I don't mind editing the source code. I'm using Java.
Thanks
Important Update:
I would like to thank everyone for taking the time to respond to this question. However, as stated in some of the comments, the first 3 answers do not address the question at all - for two reasons. First of all, using an existing Firefox Profile will not prevent Geckodriver from copying the original profile to a temporary directory (as indicated in the OP and clearly stated by one or more of the commentators below). Second, even if it did it is not compatible with Selenium 3.0.
I'm really not sure why 3 out of 4 answer repeat the exact same answer with the exact same mistake. Did they read the question? The only answer the even attempts to address the question at hand is the answer by #Life is complex however it is incomplete. Thanks.
UPDATED POST 05-30-2021
This is the hardest question that I have every tried to answer on Stack Overflow. Because it involved the interactions of several code bases written in multiple languages (Java, Rust and C++). This complexity made the question potentially unsolvable.
My last crack at this likely unsolvable question:
Within the code in your question you are modifying the file user.js This file is still used by Selenium.
public FirefoxProfile() {
this(null);
}
/**
* Constructs a firefox profile from an existing profile directory.
* <p>
* Users who need this functionality should consider using a named profile.
*
* #param profileDir The profile directory to use as a model.
*/
public FirefoxProfile(File profileDir) {
this(null, profileDir);
}
#Beta
protected FirefoxProfile(Reader defaultsReader, File profileDir) {
if (defaultsReader == null) {
defaultsReader = onlyOverrideThisIfYouKnowWhatYouAreDoing();
}
additionalPrefs = new Preferences(defaultsReader);
model = profileDir;
verifyModel(model);
File prefsInModel = new File(model, "user.js");
if (prefsInModel.exists()) {
StringReader reader = new StringReader("{\"frozen\": {}, \"mutable\": {}}");
Preferences existingPrefs = new Preferences(reader, prefsInModel);
acceptUntrustedCerts = getBooleanPreference(existingPrefs, ACCEPT_UNTRUSTED_CERTS_PREF, true);
untrustedCertIssuer = getBooleanPreference(existingPrefs, ASSUME_UNTRUSTED_ISSUER_PREF, true);
existingPrefs.addTo(additionalPrefs);
} else {
acceptUntrustedCerts = true;
untrustedCertIssuer = true;
}
// This is not entirely correct but this is not stored in the profile
// so for now will always be set to false.
loadNoFocusLib = false;
try {
defaultsReader.close();
} catch (IOException e) {
throw new WebDriverException(e);
}
}
So in theory you should be able to modify capabilities.rs in the geckodriver source code. That file contains the temp_dir.
As I stated this in only a theory, because when I looked at the Firefox source, which has temp_dir spread throughout the code base.
ORIGINAL POST 05-26-2021
I'm not sure that you can prevent Selenium from creating a temporary Firefox Profile.
From the gecko documents:
"Profiles are created in the systems temporary folder. This is also where the encoded profile is extracted when profile is provided. By default geckodriver will create a new profile in this location."
The only solution that I see at the moment would require you modify the Geckodriver source files to prevent the creation of temporary folders/profiles.
I'm currently looking at the source. These files might be the correct ones, but I need to look at the source more:
https://searchfox.org/mozilla-central/source/browser/app/profile/firefox.js
https://searchfox.org/mozilla-central/source/testing/mozbase/mozprofile/mozprofile/profile.py
Here are some other files that need to be combed through:
https://searchfox.org/mozilla-central/search?q=tempfile&path=
This looks promising:
https://searchfox.org/mozilla-central/source/testing/geckodriver/doc/Profiles.md
"geckodriver uses [profiles] to instrument Firefox’ behaviour. The
user will usually rely on geckodriver to generate a temporary,
throwaway profile. These profiles are deleted when the WebDriver
session expires.
In cases where the user needs to use custom, prepared profiles,
geckodriver will make modifications to the profile that ensures
correct behaviour. See [Automation preferences] below on the
precedence of user-defined preferences in this case.
Custom profiles can be provided two different ways:
1. by appending --profile /some/location to the [args capability],
which will instruct geckodriver to use the profile in-place;
I found this question on trying to do this: how do I use an existing profile in-place with Selenium Webdriver?
Also here is an issue that was raised in selenium on Github concerning the temp directory. https://github.com/SeleniumHQ/selenium/issues/8645
Looking through the source of geckodriver v0.29.1 I found a file where the profile is loaded.
source: capabilities.rs
fn load_profile(options: &Capabilities) -> WebDriverResult<Option<Profile>> {
if let Some(profile_json) = options.get("profile") {
let profile_base64 = profile_json.as_str().ok_or_else(|| {
WebDriverError::new(ErrorStatus::InvalidArgument, "Profile is not a string")
})?;
let profile_zip = &*base64::decode(profile_base64)?;
// Create an emtpy profile directory
let profile = Profile::new()?;
unzip_buffer(
profile_zip,
profile
.temp_dir
.as_ref()
.expect("Profile doesn't have a path")
.path(),
)?;
Ok(Some(profile))
} else {
Ok(None)
}
}
source: marionette.rs
fn start_browser(&mut self, port: u16, options: FirefoxOptions) -> WebDriverResult<()> {
let binary = options.binary.ok_or_else(|| {
WebDriverError::new(
ErrorStatus::SessionNotCreated,
"Expected browser binary location, but unable to find \
binary in default location, no \
'moz:firefoxOptions.binary' capability provided, and \
no binary flag set on the command line",
)
})?;
let is_custom_profile = options.profile.is_some();
let mut profile = match options.profile {
Some(x) => x,
None => Profile::new()?,
};
self.set_prefs(port, &mut profile, is_custom_profile, options.prefs)
.map_err(|e| {
WebDriverError::new(
ErrorStatus::SessionNotCreated,
format!("Failed to set preferences: {}", e),
)
})?;
let mut runner = FirefoxRunner::new(&binary, profile);
runner.arg("--marionette");
if self.settings.jsdebugger {
runner.arg("--jsdebugger");
}
if let Some(args) = options.args.as_ref() {
runner.args(args);
}
// https://developer.mozilla.org/docs/Environment_variables_affecting_crash_reporting
runner
.env("MOZ_CRASHREPORTER", "1")
.env("MOZ_CRASHREPORTER_NO_REPORT", "1")
.env("MOZ_CRASHREPORTER_SHUTDOWN", "1");
let browser_proc = runner.start().map_err(|e| {
WebDriverError::new(
ErrorStatus::SessionNotCreated,
format!("Failed to start browser {}: {}", binary.display(), e),
)
})?;
self.browser = Some(Browser::Host(browser_proc));
Ok(())
}
pub fn set_prefs(
&self,
port: u16,
profile: &mut Profile,
custom_profile: bool,
extra_prefs: Vec<(String, Pref)>,
) -> WebDriverResult<()> {
let prefs = profile.user_prefs().map_err(|_| {
WebDriverError::new(
ErrorStatus::UnknownError,
"Unable to read profile preferences file",
)
})?;
for &(ref name, ref value) in prefs::DEFAULT.iter() {
if !custom_profile || !prefs.contains_key(name) {
prefs.insert((*name).to_string(), (*value).clone());
}
}
prefs.insert_slice(&extra_prefs[..]);
if self.settings.jsdebugger {
prefs.insert("devtools.browsertoolbox.panel", Pref::new("jsdebugger"));
prefs.insert("devtools.debugger.remote-enabled", Pref::new(true));
prefs.insert("devtools.chrome.enabled", Pref::new(true));
prefs.insert("devtools.debugger.prompt-connection", Pref::new(false));
}
prefs.insert("marionette.log.level", logging::max_level().into());
prefs.insert("marionette.port", Pref::new(port));
prefs.write().map_err(|e| {
WebDriverError::new(
ErrorStatus::UnknownError,
format!("Unable to write Firefox profile: {}", e),
)
})
}
}
After looking through the gecko source it looks like mozprofile::profile::Profile is coming from FireFox and not geckodriver
It seems that you might have issues with profiles when you migrate to Selenium 4.
ref: https://github.com/SeleniumHQ/selenium/issues/9417
For Selenium 4 we have deprecated the use of profiles as there are other mechanisms that we can do to make the start up faster.
Please use the Options class to set preferences that you need and if you need to use an addon use the driver.install_addon("path/to/addon")
you can install selenium 4, which is in beta, via pip install selenium --pre
I noted in your code you were writing to user.js, which is a custom file for FireFox. Have you considered creating on these files manually outside of Gecko?
Also have you looked at mozprofile?
Thanks to source code provided in answer of Life is complex in link!. I have the chance to look through geckodriver source.
EXPLANATION
I believe that the reason you could not find out any rust_tmp in source because it is generated randomly by Profile::new() function.
When I look deeper in code structure, I saw that browser.rs is the place where the browser is actually loaded which is called through marionette.rs. If you noticing carefully, LocalBrowser::new method will be called whenever a new session is initialized and the profile will be loaded in that state also. Then by checking browser.rs file, there will be a block code line 60 - 70 used to actually generate profile for new session instance. Now, what need to do is modifying this path to load your custom profile.
SHORT ANSWER
Downloading zip file of geckodriver-0.30.0, extracting it by your prefer zip program :P
Looking on src/browser.rs of geckodriver source, in line 60 - 70, hoping you will see something like this:
let is_custom_profile = options.profile.is_some();
let mut profile = match options.profile {
Some(x) => x,
None => Profile::new()?,
};
Change it to your prefer folder ( hoping you know some rust code ), example:
/*
let mut profile = match options.profile {
Some(x) => x,
None => Profile::new()?,
};
*/
let path = std::path::Path::new("path-to-profile");
let mut profile = Profile::new_from_path(path)?;
Re-compile with prefer rust compiler, example:
Cargo build
NOTE
Hoping this info will help you someway. This is not comprehensive but hoping it is good enough hint for you like it is possible to write some extra code to load profile from env or pass from argument, it is possible but I'm not rust developer so too lazy for providing one in here.
The above solution is work fine for me and I could load and use directly my profile from that. Btw, I work on Archlinux with rust info: cargo 1.57.0.
TBH, this is first time I push comment on stackoverflow, so feel free to correct me if I'm wrong or produce unclear answer :P
Update
I worked in geckodriver 0.30.0 which will not be the same as geckodriver 0.29.1 mentioned by Life is complex. But the change between 2 versions just be split action, so the similar modify path in version 0.29.1 will be included in method MarionetteHandler::start_browser in file src/marionette.rs.
Since my starting point is Life is complex answer, please looking through it for more information.
I've come up with a solution that 1) works with Selenium 4.7.0--however, I don't see why it wouldn't work with 3.x as well, 2) allows the user to pass in an existing Firefox profile dynamically via an environment variable--and if this environment variable doesn't exist, simply acts "normally", and 3) if you do not want a temporary copy of the profile directory, simply do not pass the source profile directory to Selenium.
I downloaded Geckodriver 0.32.0 and made it so that you simply need to provide the Firefox profile directory via the environment variable FIREFOX_PROFILE_DIR. For example, in C#, before you create the FirefoxDriver, call:
Environment.SetEnvironmentVariable("FIREFOX_PROFILE_DIR", myProfileDir);
The change to Rust is in browser.rs, line 88, replacing:
let mut profile = match options.profile {
ProfileType::Named => None,
ProfileType::Path(x) => Some(x),
ProfileType::Temporary => Some(Profile::new(profile_root)?),
};
with:
let mut profile = if let Ok(profile_dir) = std::env::var("FIREFOX_PROFILE_DIR") {
Some(Profile::new_from_path(Path::new(&profile_dir))?)
} else {
match options.profile {
ProfileType::Named => None,
ProfileType::Path(x) => Some(x),
ProfileType::Temporary => Some(Profile::new(profile_root)?),
}
};
You may refer to my Git commit to see the diff against the original Geckodriver code.
The new driver by default creates a new profile if no options are set. To use a existing profile, one way to do this is to set the system property webdriver.firefox.profile before creating the firefox driver. A small code snippet that can create a firefox driver (given you have locations for geckodriver, and the firefox profile):
System.setProperty("webdriver.gecko.driver","path_to_gecko_driver");
System.setProperty("webdriver.firefox.profile", "path_to_firefox_profile");
WebDriver driver = new FirefoxDriver();
You could even set these system properties using the env. variables and skip defining them everywhere.
Another way to do this is to use the FirefoxOptions class which allows you to configure a lot of options. To start with, take a look at org.openqa.selenium.firefox.FirefoxDriver and org.openqa.selenium.firefox.FirefoxOptions. A small example:
FirefoxOptions options = new FirefoxOptions();
options.setProfile(new FirefoxProfile(new File("path_to_your_profile")));
WebDriver driver = new FirefoxDriver(options);
Hope this is helpful.
You can create firefox profile which will be clean and name it as SELENIUM
So When initializing the Webdriver get the profile which you have already created through the code, so that it wont create any new temp profiles all the time.
ProfilesIni allProfiles = new ProfilesIni();
FirefoxProfile desiredProfile = allProfiles.getProfile("SELENIUM");
WebDriver driver = new FirefoxDriver(desiredProfile);
That way, you assure that this profile will be used anytime you do the tests.
-Arjun
You can handle this by using --
FirefoxProfile profile = new FirefoxProfile(new File("D:\\Selenium Profile..."));
WebDriver driver = new FirefoxDriver(profile);
There is one more option but it inherits all the cookies, cache contents, etc. of the previous uses of the profile let’s see how it will be --
System.setProperty("webdriver.firefox.profile", "MySeleniumProfile");
WebDriver driver = new FirefoxDriver(...);
Hope this answers your question in short.
I've 4 test cases defined using TestNG within my Appium project and all 4 test cases performs almost same code execution besides the difference that all 4 test cases run on different parts of the app I'm automating. The issue I'm facing is within my test case I need to find an element and get its value which works fine in Test Case 1 but fails for Test Case 2, 3 and 4 respectively. However if I comment out Test Case 1, then the code works fine for Test Case 2 but fails for Test Case 3 and 4. If I run code for Test Case 1, 2, 3 or 4 individually, all of them work. I'm not able to understand why the code works only for the first test case in execution but fails to find the element in the next test case.
Note that the only option for me to find element is using absolute xpath value since the app I'm trying to automate does not have any other attribute defined.
Environment
java client 4.1.2
Appium server version: v1.7.2
Selenium: v3.4.0
Desktop OS/version used to run Appium if necessary: Windows 10 x64
Mobile platform/version under test: Fire TV Stick running on Android 5.1.1
Real device or emulator/simulator: Real device
Details
I've provided the code for Test1 below. As mentioned earlier, Test2, Test3 and Test4 are same as Test1. The code below is the one which fails from second Test onwards even though the code works fine when tested individually and the element is definitely present on the screen when the code is executed. However the script returns no element found.
Failing code
strTime1 = driver.findElementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.FrameLayout/android.widget.FrameLayout[3]/android.widget.FrameLayout[3]/android.widget.FrameLayout[2]/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.TextView").getText();
Code To Reproduce Issue
#test //Test Case for Home tab
public void Test1_Home() throws InterruptedException, ParseException{
Thread.sleep(delay3); //Wait for 10 seconds to load the content
driver.findElementByXPath("//android.widget.FrameLayout[#content-desc="Home, Button"]").click(); //Code to click Home button
Thread.sleep(delay2); //Wait for 10 seconds to load the content
((AndroidDriver) driver).pressKeyCode(23); //Code to click first video thumbnail button
Thread.sleep(delay2); //Wait for 10 seconds to load the content
//Video pause verification code starts
((AndroidDriver) driver).pressKeyCode(23); //Pause the video
Thread.sleep(1000); //Wait for 1 seconds
strTime1 = driver.findElementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.FrameLayout/android.widget.FrameLayout[3]/android.widget.FrameLayout[3]/android.widget.FrameLayout[2]/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.TextView").getText();
System.out.println(strTime1);
Thread.sleep(delay1); //Wait for 5 seconds
strTime2 = driver.findElementByXPath("/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.FrameLayout[1]/android.widget.FrameLayout/android.widget.FrameLayout[3]/android.widget.FrameLayout[3]/android.widget.FrameLayout[2]/android.widget.FrameLayout/android.widget.FrameLayout/android.widget.TextView").getText();
System.out.println(strTime2);
time1 = time.parse(strTime1);
time2 = time.parse(strTime2);
System.out.println(time1);
System.out.println(time2);
if(String.valueOf(strTime1).equals(String.valueOf(strTime2)))
{
boolean result = true; //Set result to true
System.out.println("Home: Video playback pause successful");
Assert.assertTrue(result); //Passes the test case
}
else
{
boolean result = false; //Set result to false
System.out.println("Home: Video playback pause failed");
Assert.assertTrue(result); //Fails the test case
}
//Video pause verification code ends
Thread.sleep(delay2); //Allow the video to play for 10 seconds including buffering time
}
Exception stack traces
org.openqa.selenium.NoSuchElementException: An element could not be located on the page using the given search parameters. (WARNING: The server did not provide any stacktrace information)
Command duration or timeout: 10.13 seconds
Let me know if any further information is required. Any kind of help would be appreciated
could you please try to locate the element with relative xpath or accesability id?
Also it is always best practice to use latest versions of appium server and java client as they are continuously making changes to the appium.
below is the link for latest java client if you are using maven : https://mvnrepository.com/artifact/io.appium/java-client
I wanted to use the Braintree Marketplace (I've got the approval and signed to use the marketplace functionality). I used JAVA for my backend and played with the Sandbox account.
I was trying to create a submerchant accorinding to the tutorials in the overview and the specific section. E.g., the code I directly copied from the guide is (I'VE ALSO TRIED TO USE MY VALID PERSONAL INFORMATION BELOW):
MerchantAccountRequest request = new MerchantAccountRequest().
individual().
firstName("Jane").
lastName("Doe").
email("jane#14ladders.com").
phone("5553334444").
dateOfBirth("1981-11-19").
ssn("456-45-4567").
address().
streetAddress("111 Main St").
locality("Chicago").
region("IL").
postalCode("60622").
done().
done().
funding().
descriptor("Blue Ladders").
destination(MerchantAccount.FundingDestination.BANK).
email("funding#blueladders.com").
mobilePhone("071101307").
accountNumber("1123581321").
routingNumber("071101307").
done().
tosAccepted(true).
masterMerchantAccountId("14ladders_marketplace").
id("blue_ladders_store");
Result<MerchantAccount> result = gateway.merchantAccount().create(request);
I've also tried to use the testing first name
com.braintreegateway.test.MerchantAccount.Approve
so that the creation should always succeed. The code snippet is:
Result<MerchantAccount> result = gateway.merchantAccount().create(
new MerchantAccountRequest().
individual()
.firstName(com.braintreegateway.test.MerchantAccount.Approve)
.done()
);
However, in any case, when I tried to check if the result is successful (and if not, what the errors are):
logger.info("Is the result successful? " + result.isSuccess());
ValidationErrors errors = result.getErrors();
logger.info("Number of errors returned " + result.getErrors().size());
The result.isSuccess() always returns false, while there are no errors in result.getErrors(). I also cannot find any (error) information from the sandbox web account. Any insights what might go wrong? or is this because I haven't enabled the marketplace functionality?
Thanks!
I work at Braintree. If you have more questions, you can always get in touch with our support team. It looks like you already have an open ticket with us and got a response, but I'll post his here for others who have the same problem.
You're looking for result.getErrors().getAllDeepValidationErrors() not just result.getErrors().
For more information on this type of failure, see the validation errors page in the Braintree docs.
I'm just starting out with Geb and am encountering this error when inputting sample code from the Book of Geb:
import geb.Browser
Browser.drive {
go "http://google.com/ncr"
// make sure we actually got to the page
assert title == "Google"
// enter wikipedia into the search field
$("input", name: "q").value("wikipedia")
// wait for the change to results page to happen
// (google updates the page dynamically without a new request)
waitFor { title.endsWith("Google Search") }
// is the first link to wikipedia?
def firstLink = $("li.g", 0).find("a.l")
assert firstLink.text() == "Wikipedia"
// click the link
firstLink.click()
// wait for Google's javascript to redirect to Wikipedia
waitFor { title == "Wikipedia" }
}
I am encountering this exception:
Caught: java.lang.IncompatibleClassChangeError: the number of constructors during runtime and compile time for java.lang.Exception do not match. Expected 4 but got 5
at geb.error.GebException.<init>(GebException.groovy:20)
at geb.waiting.WaitTimeoutException.<init>(WaitTimeoutException.groovy:30)
at geb.waiting.Wait.waitFor(Wait.groovy:108)
.......
Any ideas? Thanks!
Are you using Java 7 by any chance? Groovy code that uses exceptions that was compiled with < Java 7 is not compatible with Java 7.
Geb is compatible with Java7 as of 0.7.1. If you are below that you should upgrade. SEe: http://jira.codehaus.org/browse/GEB-194