Search code examples
geb

Geb - How to remove attribute in Geb


I want to set value for a datepicker, it has attribute 'readonly'. I tried removeAttr() to remove the attribute, but got error like this:

Caught: groovy.lang.MissingMethodException: No signature of method: geb.navigator.NonEmptyNavigator.removeAttr() is applicable for argument types: (java.lang.String) values: [readonly]

How can I remove an attribute in Geb?

Souce code:

<input ng-show="!date" class="ui-form-control ng-isolate-scope" name="date" placeholder="StartTime" ui-datepicker-trigger="" selected-date="date" readonly="readonly" type="text">

my geb code:

$('input', placeholder: 'StartTime').removeAttr('readonly')

Thanks all


Solution

  • Because the OP said it was an AngularJS datepicker and I never had any contact with Angular, myself not being a front-end guy, I wanted to learn something and now I am sharing it with you, for whatever it is worth.

    Here is a Geb test which does not use any JS, but gathers information from and interacts with the Angular datepicker purely by means of Geb or Selenium. The test shows how to

    1. get the currently selected date from the datepicker's text field via myNavigator.singleElement().getAttribute("value") (also works for inactive controls),
    2. get the currently selected date by opening the datepicker and finding the highlighted date in the calendar (only works for active controls, of course),
    3. rather tediously interact with the datepicker, selecting the current year's Xmas Day (Dec-25) by
      • opening the datepicker,
      • clicking on the current month in order to open the month of year selector,
      • clicking on December, effectively closing the month selector again,
      • clicking on day 25, effectively closing the datepicker again,
      • finally checking whether the right date was set by reading the text label just like in example #1.

    Page:

    package de.scrum_master.stackoverflow
    
    import geb.Page
    import geb.navigator.Navigator
    
    import java.time.Month
    import java.time.format.TextStyle
    
    import static java.util.Calendar.*
    
    class DatePickerPage extends Page {
      static url = "https://material.angularjs.org/1.1.2/demo/datepicker"
      static at = { heading == "Datepicker" }
      static now = new GregorianCalendar()
    
      static content = {
        heading { $("h2 > span.md-breadcrumb-page.ng-binding").text() }
        datePickerButtons { $("md-datepicker > button") }
        datePickerInputFields { $(".md-datepicker-input") }
        activeDatePicker(required: false) { $(".md-datepicker-calendar-pane.md-pane-open") }
        selectedDate { activeDatePicker.$(".md-calendar-selected-date") }
        currentMonthLabel {
          activeDatePicker
            .$("td.md-calendar-month-label", text: "${getMonthShort(now)} ${now.get(YEAR)}")
        }
        selectedMonth(required: false) { $("td.md-calendar-selected-date") }
      }
    
      String getDatePickerValue(Navigator datePickerInputField) {
        datePickerInputField.singleElement().getAttribute("value")
      }
    
      Navigator getMonthLabel(Calendar calendar) {
        $(".md-calendar-month-label", text: "${calendar.get(YEAR)}").closest("tbody")
          .$("span", text: getMonthShort(calendar)).closest("td")
      }
    
      Navigator getDayOfMonthLabel(Calendar calendar) {
        activeDatePicker
          .$(".md-calendar-month-label", text: "${getMonthShort(calendar)} ${calendar.get(YEAR)}")
          .closest("tbody")
          .$("span", text: "${calendar.get(DAY_OF_MONTH)}")
          .closest("td")
      }
    
      String getMonthShort(Calendar calendar) {
        Month
          .of(calendar.get(MONTH) + 1)
          .getDisplayName(TextStyle.FULL, new Locale("en"))
          .substring(0, 3)
      }
    }
    

    Test:

    package de.scrum_master.stackoverflow
    
    import geb.module.FormElement
    import geb.spock.GebReportingSpec
    import spock.lang.Unroll
    
    import static java.util.Calendar.*
    
    class DatePickerIT extends GebReportingSpec {
      def now = new GregorianCalendar()
      def xmas = new GregorianCalendar(now.get(YEAR), 12 - 1, 25)
    
      @Unroll
      def "Get selected date from Angular date label"() {
        when: "a date picker on the Angular demo page is chosen"
        DatePickerPage page = to DatePickerPage
        def datePickerInputField = page.datePickerInputFields[datePickerIndex]
    
        then: "that date picker instance is (in-)active as expected"
        datePickerInputField.module(FormElement).enabled == isEnabled
    
        when: "the active date is read from the date picker's text input field"
        String activeDateString = page.getDatePickerValue(datePickerInputField)
        String todayString = "${now.get(MONTH) + 1}/${now.get(DAY_OF_MONTH)}/${now.get(YEAR)}"
    
        then: "it is today"
        todayString == activeDateString
    
        where:
        datePickerIndex << [0, 1, 2, 3, 4, 5, 6, 7, 8]
        isEnabled << [true, false, true, true, true, true, true, true, true]
      }
    
      @Unroll
      def "Get selected date from opened Angular date picker"() {
        when: "a date picker on the Angular demo page is chosen"
        DatePickerPage page = to DatePickerPage
        def datePickerButton = page.datePickerButtons[datePickerIndex]
    
        then: "that date picker instance is active (not greyed out)"
        datePickerButton.module(FormElement).enabled
    
        when: "the calendar button for the date picker is clicked"
        datePickerButton.click()
    
        then: "the date picker pop-up opens, displaying the current month"
        waitFor 3, { page.activeDatePicker.displayed }
    
        when: "the active date's timestamp is read from the highlighted day in the calendar"
        def selectedDateMillis = page.selectedDate.attr("data-timestamp") as long
        def sinceMidnightMillis = now.getTimeInMillis() - selectedDateMillis
    
        then: "it is today"
        sinceMidnightMillis >= 0
        sinceMidnightMillis < 24 * 60 * 60 * 1000
    
        where:
        datePickerIndex << [0, 2, 4, 5, 6, 7, 8]
      }
    
      def "Set date in Angular date picker"() {
        when: "the first date picker's calendar button on the Angular demo page is clicked"
        DatePickerPage page = to DatePickerPage
        page.datePickerButtons[0].click()
    
        then: "the date picker pop-up opens, displaying the current month"
        waitFor 3, { page.activeDatePicker.displayed }
    
        when: "the current month label is clicked"
        page.currentMonthLabel.click()
    
        then: "the month selection page opens"
        waitFor 3, { page.selectedMonth.displayed }
        page.selectedMonth.text() == page.getMonthShort(now)
    
        when: "December for the current year is selected"
        page.getMonthLabel(xmas).click()
    
        then: "the month selection page closes again"
        waitFor 3, { !page.selectedMonth.displayed }
    
        when: "Xmas Day (25) is selected on the month calendar"
        page.getDayOfMonthLabel(xmas).click()
    
        then: "the date picker pop-up closes again"
        waitFor 3, { !page.activeDatePicker.displayed }
    
        when: "the active date is read from the date picker's text input field"
        String activeDateString = page.getDatePickerValue(page.datePickerInputFields[0])
        String xmasDayString = "${xmas.get(MONTH) + 1}/${xmas.get(DAY_OF_MONTH)}/${xmas.get(YEAR)}"
    
        then: "it is Xmas Day of the current year"
        xmasDayString == activeDateString
      }
    
    }
    

    The advantage of this approach is obvious: You only interact with the page in a way the user would/could also do, thus avoiding to manipulate the page in a way impossible for a user (which would result in a bad test).

    Update: I refactored the code into using a page object.