diff --git a/README.md b/README.md index 3a446cf..72b5bd3 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,128 @@ -# Quality Assurance Exercise - -# Overview - -Here at ShootProof, we prefer to have a good idea of a candidate's technical -experience and analysis skills before proceeding with portions of our recruiting -process. We believe that the exercises below will illustrate a candidate's -approach to working with technologies and methodologies commonly used in our -QA team here at ShootProof. - -# Guidelines - -The exercise consists of several different parts designed to showcase your -problem-solving and solution-implementation talents. There is not a single -correct answer; this is *not* an exam. We simply want to see how you approach a -business problem and the steps you take to solve it. - -* This exercise should not take you more than two hours to complete. If - your solution is taking longer, that's okay—be honest and let us know how long - it took and why you think it took that long. -* Be as thorough as you wish. -* All exercises are to be performed as if you were on the job. -* You may submit your response in one of the following ways: - * Package an archive (ZIP, tarball, etc.) of your files and deliver it to - your contact. - * If working with a recruiter, deliver it to them. - * If working with ShootProof directly, deliver to . - * Fork our repository and open a pull request. - -# Exercise: Testing a product backlog item as a Scrum development team member - -* It's your first week at ShootProof. Your cross-functional Scrum team, which includes specialists in design and sotware engineering, has refined and planned a user story. Unfortunately you've missed these working meetings. -* Here's what they've recorded: -___ -## User story: As a studio, I want to sign up my studio for a ShootProof trial. - ->Acceptance Criteria: ->* Fast, correctly, and with no training - ->Value Path: -> ->Photographer learns about ShootProof -> Visits shootproof.com -> **Signs up for trial** -> Tries the product -> Purchases plan - ->Dev team notes: ->* API needs updates to include trial duration period ->* Frontend needs to update fonts and images for signup page ->* Designs are not finalized, but will need to be consistent with the rest of the site ->* Testing should include at least some automation, so we always know new studios can sign up ->* Testing should include regression of regular login - -___ - -## Your task - -### Answer the following questions - -* What are some questions you'd ask your team to ensure test coverage? - * Who would you ask each of these questions? All Scrum team members are available. - * Make assumptions about the answers you'd get. Note your assumptions. -* Once you determine how to facilitate test coverage, given your assumptions, - * How would you communicate your plan? - * How would you accomplish execution of that plan? - - -### Automate the following test case - -Provide an automated test case to check that a visitor to can proceed to the Signup screen. Use the tool and language of your choice (Selenium, etc.). - -A complete submission will: - -* Load -* Click one of the "Get Started" buttons -* Check that the ShootProof signup form appears (check the URL, the page contents, or both as you see fit) - * The test should fail if your check(s) are not met (i.e., use assertions) -* Explain why you chose the language and framework that you did -* If the code and approach is not obvious, describe the approach and decisions - made - -In the event that you cannot provide working code, please provide a written -description of how you would locate and click this button. Pseudocode is acceptable, -but working code is preferred. - -# (Optional) Exercise: JsonPath - -**Goal:** locate specific values within a JSON object using JsonPath - -ShootProof uses Karate for API testing. Part of this work is writing -[JsonPath selectors](https://github.com/json-path/JsonPath#filter-operators) to select a -particular part of a JSON object. The concept should be familiar if you’ve written -relative XPaths or SQL queries. - -Using the JSON source data below, complete the following exercises. -You may use to check your work. - -1. Write a JsonPath that gets `totalItems` from `meta` -1. Write a JsonPath that gets the whole `contact` object for contacts with at least two galleries -1. Write a JsonPath that gets the `tags` for the `contact` object where the contact name is "Adrastea Shahriar" - -A complete submission will: - -* Include a JsonPath statement for each of the three questions above - -### Source data - -Use the JSON data below for the following questions: - -```JSON -{ - "meta": { - "currentPage": 1, - "totalPages": 1, - "totalItems": 5, - "rows": 5 - }, - "contact": [ - { - "id": 5, - "name": "Manuel Makarios", - "email": "mmakarios@gmail.com", - "tags": ["wedding"], - "galleriesCount": 2 - }, - { - "id": 6, - "name": "Kyriake Žarko", - "email": "zarkok@yahoo.com", - "tags": [], - "galleriesCount": 0 - }, - { - "id": 7, - "name": "Tsholofelo Wacława", - "email": "Tsholofelo@test.com", - "tags": ["wedding"], - "galleriesCount": 1 - }, - { - "id": 8, - "name": "Adrastea Shahriar", - "email": "adrastea.shahriar@mail.com", - "tags": ["wedding", "family"], - "galleriesCount": 5 - }, - { - "id": 9, - "name": "Jacob Klava", - "email": "jklava7@aol.com", - "tags": ["graduation", "family"], - "galleriesCount": 3 - } - ] -} -``` +Thank you in advance for reviewing my answer. + +# Exercise I (90 mins) + +I spent time being as thorough as possible. Came up with a draft, revisited and revised it several times. + +## User Story 1 : As a Studio User, I want to track client referrals so that I can provide great customer experience and grow my client base + +* Questions on the story: + * Is there any backend dependency that this front end ticket requires? If there is it would be great to link the backend stories to this ticket. +That will help QA understand more of payload, behavior and logic + * Looks like we are only adding a new section referral on the existing Contact Edit Page? Or there are fields that exist today but will be removed or updated after this story? +If so any impact on the existing system? + * Is this new Referral section required or optional for a contact? Looking at the visual the section is optional(does not have * marked) but a statement of so will definitely help confirm + * The Acceptance Criteria (AC) says: "Typing in either text field searches the studio’s existing contact list by name and email". + * Is there a visual to show how the list looks like when I type in either text field? + * What if the studio only has the current contact? If I click on "+" from either Referred By or Referred these Contacts what is supposed to happen? + * When searching by name would the app tries match any string in any of first, middle and last names? Or just first name? + * What if I type in invalid email format or characters not accepted by the system? + * If a contact info is changed would that change (say name) reflect on the list under either Refer section? I believe so but a callout on AC would be nice. + * What if a user accidentally types in the same contact who the Edit page is associated with under either new text field? Will the same contact be supposed to show up? For example if I load a contact edit page for Mary and then I try add Mary as her own Referrer and Referee? + * How many contacts a user can add under either Refer section? What would the UI look like if the list is very long? + * What if a user tries adding a same contact under both Referrer and Referee? For example I try add a same Mike under both sections? + * The AC also says "the correct relationship should appear on both contacts' info pages". Is there a visual that illustrates what will be displayed on info page? + * From where would I come to Contact Edit Page? + * Where would a user be directed to respectively when she/he clicks on Save and Cancel buttons? + * What are the error messages that a user will see when saving changes? + * What browser types/versions are we supposed to support? Are we doing responsive for desktop and mobile? A link to such info would help QA determine testing scope + * What are the styling requirements ..for example .. font/size/color/spacing and etc? + * What if the response returns an error code such as 400, 403, 500 and the list can go on? What error messages are expected and where/how will they be displayed? + + +* Test cases: + * Verify when page is loaded the existing contact info is properly populated + * Verify that a user can add contacts under both new text fields: land on correct page, all changes are saved, contacts show up on contact info page. + * Verify that a user can add contacts under either but not both new text fields. + * Verify a user can save without adding any contact under either new text field (if UX confirms Referral section is optional) + * Verify a user clicks Cancel button and lands on the correct page with all changes being discarded. + * Verify the expected behavior (once UX adds that in) on typing in either field when no other existing contacts are present + * Verify a user cannot add the same contact who the Edit page is associated with under either new text field + * Verify a user cannot add a same contact as both Referrer and Referee? + * Verify a user cannot save a page if any required field is not provided + * Verify when adding many contacts under either Referrer or Referee the UI would show an expandable list (needs UX to confirm and provide a visual) + * Verify proper error messages show up when invalid value are provided (length, allowed characters and etc) + * Verify an unauthenticated user cannot edit a contact + * Verify an unauthorized user cannot edit a contact that does not belong to him/her + * Besides functional testing depending on business/technical needs and site volume we may need to do security and/or performance testing + +## User Story 2 : As a studio, I want to create a contact to manage information necessary for my photography business. + +* Questions on the story: + * First / last name and email fields + * What is the max allowed length for each field? + * What are the allowed characters for each field? + * Would the app automatically capitalize either name? + * What are the exact error messages when any of the three field is empty/too long, has invalid characters, a user adds an email that already exists and etc? + * What is the styling on error messages? + * Phone + * What is the expected phone number format? Would the app automatically populate "-" at the right places? + * Would we allow fake numbers such as "000-000-0000"? Or in another world what are the validation rules? + * What are the exact error messages when an invalid phone numbe is provided? + * Where to locate error messages? + * What is the styling on error messages? + * Date of Birth + * How far a dob can go in the past? For example can I choose year 1800? + * What is considered a valid birthday? For example we would probably only allow create a contact who is at least 18 years old? + * Address/City/Postcode/Country/State + * If I enter info into one field what other fields are required? For example can I just provide country/state and leave address empty? Or can I provide an address without contry, state or zipcode? What are the validation rules? How about I provide a non-existent state? + * Does the app need to verify if the address exists, zip code / post code is valid? + * Where would the State field be displayed? A visual will help + * What are the validation rules on each field? + * Business Name + * No AC is specified for Business Name field + * What is the max allowed length? + * What are the allowed characters? + * What and where is the error message when the validation fails? + * Notes + * No AC is specified for notes field + * What is the max allowed length? + * What and where is the error message when that max length is exceeded? + * Tags + * I would suggest move AC for tags into a different story as what are done for other sections (galleries, contracts and etc) on the right hand side + * How many tags can a user add? Would the tag list be collapsed if the list contains a certain numbers of tags? What would UI look like? + * Would a user type in any tag or the app will provide a fixed list for the user to choose from? + * AC says: "A contact will only have tags saved if the contact is saved". What info a contact has will have him/her to be considered saved? Is that as long as all the three required info (first name, last name and emmail) is saved then we can say a contact is saved? Or we need to check all info on the left hand side on the screenshot before tags info can be saved? Do we need to also check if referrals sections is successfully saved before persisting tags? + * Is there any backend dependency that this front end ticket requires? + * From where would I come to Contact Edit Page? + * Where would a user be directed to respectively when she/he clicks on Save and Cancel buttons? + * What browser types/versions are we supposed to support? Are we doing responsive for desktop and mobile? A link to such info would help QA determine testing scope + * What are the styling requirements ..for example .. font/size/color/spacing and etc? + * What if the response returns an error code such as 400, 403, 500 and the list can go on? What error messages are expected and where/how will they be displayed? +* Test cases: + * Verify when page is loaded with all the expected fields + * Verify that a user can add contacts by only providing first name, last name and email, all changes are saved and the user is redirected to the correct destination page. + * Verify that a user can add contacts by providing required + optional info. + * Verify a user clicks Cancel button and lands on the correct page with all changes being discarded. + * Verify a user cannot add an existing contact + * Verify a user cannot save a page if any required field is not provided + * Verify a user cannot save a page if invalid input is provided for any field, and proper errors show up (validation rules / error UI have to be fleshed out with UX. + And I see there will be several test cases to cover all the validation) + * Verify an unauthenticated user cannot edit a contact + * Verify an unauthorized user cannot edit a contact that does not belong to him/her + * Besides functional testing depending on business/technical needs and site volume we may need to do security and/or performance testing + +# Exercise II (2 hours) + +The design came straight up with no delay: use page object pattern, set up tests, pages, locators, util folders. The main idea is that a test interacts with one or more pages. Each page interacts with locators + +What I spent most of time are: +* Set up eclipse on my home machine. The latest build for some reason behaved oddly, for example I had to restart the ide multiple times in order to link to github, and to add jars onto classpath within the ide. +* I have not programmed with java / testng for quite a while. So it took me some time although not long to warm up. The reason why I chose the combo is I really like the clarity testng provides through xml file and annotations. On top of that java / testng both hava huge user community and anyone can get great support in a short time. +* The bottom most button is out of viewport and becomes no clickable on the chrome session initiated by selenium. So I did a bit research to find the right solution. + * BTW the bottom most button used to be "GET STARTED" but it is now "CREATE YOUR FREE GALARIES". I saw the former earlier in the day Weds but the latter during the night on the same day. Maybe an A/B test which seems to be the right guess per the findings from the recruiting company. + * Also there are actually three GET STARTED buttons on the home page. The 3rd will show up at the top right corner when we scroll down and the 1st GET STARTED button is not within the viewport. My test code does not interact with that 3rd button. + +All of the above especially the first two contributed to the extended effort. + +To run the project we will need: +* TestNG http://testng.org/doc/download.html - I used Eclipse, installed TestNG plugin and added its library onto the classpath +* Selenium Java Client https://www.seleniumhq.org/download/ - Required jars are included under libs folder +* Selenium Standalone Server https://www.seleniumhq.org/download/ +* chromedriver https://www.seleniumhq.org/download/ + + +# Exercise III (6 minutes) +* $.meta.totalItems - Write a JsonPath that gets totalItems from meta +* $.contact[?(@.galleriesCount>=2)] - Write a JsonPath that gets the whole contact object for contacts with at least two galleries +* $.contact[?(@.name=='Adrastea Shahriar')].tags[*] - Write a JsonPath that gets the tags for the contact object where the contact name is "Adrastea Shahriar" diff --git a/libs/byte-buddy-1.8.15.jar b/libs/byte-buddy-1.8.15.jar new file mode 100644 index 0000000..b8c8379 Binary files /dev/null and b/libs/byte-buddy-1.8.15.jar differ diff --git a/libs/client-combined-3.141.59.jar b/libs/client-combined-3.141.59.jar new file mode 100644 index 0000000..a8b87b5 Binary files /dev/null and b/libs/client-combined-3.141.59.jar differ diff --git a/libs/commons-exec-1.3.jar b/libs/commons-exec-1.3.jar new file mode 100644 index 0000000..9a64351 Binary files /dev/null and b/libs/commons-exec-1.3.jar differ diff --git a/libs/guava-25.0-jre.jar b/libs/guava-25.0-jre.jar new file mode 100644 index 0000000..79a744e Binary files /dev/null and b/libs/guava-25.0-jre.jar differ diff --git a/libs/okhttp-3.11.0.jar b/libs/okhttp-3.11.0.jar new file mode 100644 index 0000000..aa428e6 Binary files /dev/null and b/libs/okhttp-3.11.0.jar differ diff --git a/libs/okio-1.14.0.jar b/libs/okio-1.14.0.jar new file mode 100644 index 0000000..a0ee347 Binary files /dev/null and b/libs/okio-1.14.0.jar differ diff --git a/shootproof/selenium/locators/HomePageLocators.java b/shootproof/selenium/locators/HomePageLocators.java new file mode 100755 index 0000000..a98f915 --- /dev/null +++ b/shootproof/selenium/locators/HomePageLocators.java @@ -0,0 +1,6 @@ +package shootproof.selenium.locators; + +public class HomePageLocators { + public static final String TOP_MOST_GET_STARTED_BTN = "//p[@id='navbar-button-trigger']/a"; + public static final String BOTTOM_MOST_GET_YOUR_FREE_GALLERY_BTN = "//div[@id='try-free']/div[@class='container']/p[2]/a"; +} diff --git a/shootproof/selenium/pages/BasePage.java b/shootproof/selenium/pages/BasePage.java new file mode 100755 index 0000000..5f902a2 --- /dev/null +++ b/shootproof/selenium/pages/BasePage.java @@ -0,0 +1,26 @@ +package shootproof.selenium.pages; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedCondition; +import org.openqa.selenium.support.ui.WebDriverWait; + +import shootproof.selenium.utils.Constants; + +public class BasePage { + private WebDriver webDriver; + + public BasePage (WebDriver webDriver) { + this.webDriver = webDriver; + } + + protected WebDriver getWebDriver () { + return this.webDriver; + } + + protected WebElement waitOnCondition(ExpectedCondition condition){ + WebDriverWait wait = new WebDriverWait(this.getWebDriver(), Constants.FIVE_SECS); + WebElement element = wait.until(condition); + return element; + } +} diff --git a/shootproof/selenium/pages/HomePage.java b/shootproof/selenium/pages/HomePage.java new file mode 100755 index 0000000..6d58b2c --- /dev/null +++ b/shootproof/selenium/pages/HomePage.java @@ -0,0 +1,59 @@ +package shootproof.selenium.pages; + +import org.openqa.selenium.By; +import org.openqa.selenium.JavascriptExecutor; +import org.openqa.selenium.TimeoutException; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.support.ui.ExpectedConditions; + +import shootproof.selenium.locators.HomePageLocators; + +public class HomePage extends BasePage{ + public HomePage(WebDriver targetWebDriver) { + super(targetWebDriver); + } + + public void lauch() { + this.getWebDriver().get("https://shootproof.com/"); + } + + public void scrollToPageBottom() { + ((JavascriptExecutor) this.getWebDriver()).executeScript("window.scrollTo(0, document.body.scrollHeight)"); + } + + public void waitForTopMostGetStartedBtnToDisplay() { + this.waitOnCondition(ExpectedConditions.visibilityOfElementLocated(By.xpath(HomePageLocators.TOP_MOST_GET_STARTED_BTN))); + } + + public boolean isBottomMostGetYourFreeGalleryBtnDisplayed() { + WebElement bottomMostGetStartedBtn = this.getWebDriver().findElement(By.xpath(HomePageLocators.BOTTOM_MOST_GET_YOUR_FREE_GALLERY_BTN)); + return bottomMostGetStartedBtn.isDisplayed(); + } + + public boolean isBottomMostGetYourFreeGalleryBtnClickable() { + try { + this.waitOnCondition(ExpectedConditions.elementToBeClickable(By.xpath(HomePageLocators.BOTTOM_MOST_GET_YOUR_FREE_GALLERY_BTN))); + return true; + } catch (TimeoutException e) { + return false; + } + } + + public void scrollBottomMostGetYourFreeGalleryBtnIntoView(){ + WebElement bottomMostGetStartedBtn = this.getWebDriver().findElement(By.xpath(HomePageLocators.BOTTOM_MOST_GET_YOUR_FREE_GALLERY_BTN)); + JavascriptExecutor jse2 = (JavascriptExecutor)this.getWebDriver(); + jse2.executeScript("arguments[0].scrollIntoView()", bottomMostGetStartedBtn); + } + + public void clickTopMostGetStartedBtn() { + WebElement topMostGetStartedBtn = this.getWebDriver().findElement(By.xpath(HomePageLocators.TOP_MOST_GET_STARTED_BTN)); + topMostGetStartedBtn.click(); + } + + public void clickBottomMostGetYourFreeGalleryBtn() { + WebElement bottomMostGetStartedBtn = this.getWebDriver().findElement(By.xpath(HomePageLocators.BOTTOM_MOST_GET_YOUR_FREE_GALLERY_BTN)); + bottomMostGetStartedBtn.click(); + } + +} diff --git a/shootproof/selenium/tests/BaseTest.java b/shootproof/selenium/tests/BaseTest.java new file mode 100755 index 0000000..68fb1fe --- /dev/null +++ b/shootproof/selenium/tests/BaseTest.java @@ -0,0 +1,39 @@ +package shootproof.selenium.tests; + +import java.net.URL; + +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.remote.DesiredCapabilities; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.testng.annotations.AfterSuite; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Parameters; + +public class BaseTest{ + private WebDriver targetWebDriver; + + @BeforeSuite (alwaysRun = true) + @Parameters({"browser","version"}) + protected void setupSuite(String browserName, String browserVersion) throws Exception { + DesiredCapabilities capabilities = new DesiredCapabilities(); + capabilities.setBrowserName(browserName); + capabilities.setVersion(browserVersion); + + try{ + targetWebDriver = new RemoteWebDriver( + new URL("http://localhost:4444/wd/hub"), + capabilities); + }catch(Exception e) { + throw new Exception("Exception is thrown during test setup: " + e.getMessage()); + } + } + + protected WebDriver getTargetWebDriver() { + return targetWebDriver; + } + + @AfterSuite (alwaysRun = true) + protected void teardownSuite (){ + targetWebDriver.close(); + } +} diff --git a/shootproof/selenium/tests/TestGetStartedButtonsOnHomePage.java b/shootproof/selenium/tests/TestGetStartedButtonsOnHomePage.java new file mode 100755 index 0000000..9106e70 --- /dev/null +++ b/shootproof/selenium/tests/TestGetStartedButtonsOnHomePage.java @@ -0,0 +1,36 @@ +package shootproof.selenium.tests; + +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import shootproof.selenium.pages.HomePage; +import shootproof.selenium.utils.Constants; + +public class TestGetStartedButtonsOnHomePage extends BaseTest{ + private HomePage homepage; + + @BeforeMethod + public void launchHomepage() { + homepage = new HomePage(this.getTargetWebDriver()); + homepage.lauch(); + } + + @Test + public void testClickTopMostGetStartedButton() { + homepage.waitForTopMostGetStartedBtnToDisplay(); + homepage.clickTopMostGetStartedBtn(); + } + + @Test + public void testClickBottomMostGetYourFreeGalleryButton() throws InterruptedException { + homepage.scrollToPageBottom(); + Assert.assertTrue(homepage.isBottomMostGetYourFreeGalleryBtnDisplayed(), "Get Started button is Not showing up at the bottom of the page"); + int num_retry = 0; + while (num_retry < Constants.MAX_RETRIES && !homepage.isBottomMostGetYourFreeGalleryBtnClickable()) { + homepage.scrollBottomMostGetYourFreeGalleryBtnIntoView(); + } + homepage.clickBottomMostGetYourFreeGalleryBtn(); + } + +} diff --git a/shootproof/selenium/utils/Constants.java b/shootproof/selenium/utils/Constants.java new file mode 100755 index 0000000..dc90802 --- /dev/null +++ b/shootproof/selenium/utils/Constants.java @@ -0,0 +1,10 @@ +package shootproof.selenium.utils; + +public class Constants { + public static int THREE_SECS = 3; + public static int FIVE_SECS = 5; + public static int TEN_SECS = 10; + + public static int MAX_RETRIES = 3; + +} diff --git a/testng.xml b/testng.xml new file mode 100755 index 0000000..163e30e --- /dev/null +++ b/testng.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + +