问题
I would like to create GENERIC python code which will fill in the day, month and year to the correct field.
BUT not sure how to.
In the first example, we can see that we have a linkage between label
and input
by using for
parameter (BUT we need to take for
parameter from the label and somehow check if it will contain DAY, MONTH and e.t.c..)
In the second example, we can see that input
does not have any IDs, by the span
wrapper. So we can link label
with span
and then somehow by using following::/input[0,1,2]
find DAY,MONTH and e.t.c..
QUESTION: is it any possible way to create generic code which will work for the first and second examples? ANy best practices?
First website:
<div data-readonly="False">
<label for="Applicants_0__DateOfBirth">Date of birth</label>
<div class="rhsForm">
<input type="text" class="split-date-hidden hasDatepicker" id="dp1587032701992">
<input class="split-date-day" data-val="true" data-val-number="Please enter a valid date (day)" data-val-range="Please enter a valid date (day)" data-val-range-max="31" data-val-range-min="1" id="Applicants_0__DateOfBirth_Day" maxlength="2" name="Applicants[0].DateOfBirth.Day" placeholder="DD" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="Applicants[0].DateOfBirth.Day" data-valmsg-replace="true"></span>
<span class="split-date-separator"> </span>
<input class="split-date-month" data-val="true" data-val-number="Please enter a valid date (month)" data-val-range="Please enter a valid date (month)" data-val-range-max="12" data-val-range-min="1" id="Applicants_0__DateOfBirth_Month" maxlength="2" name="Applicants[0].DateOfBirth.Month" placeholder="MM" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="Applicants[0].DateOfBirth.Month" data-valmsg-replace="true"></span>
<span class="split-date-separator"> </span>
<input class="split-date-year" data-val="true" data-val-number="Please enter a valid date (year)" data-val-range="Please enter a valid date (year)" data-val-range-max="5000" data-val-range-min="1000" id="Applicants_0__DateOfBirth_Year" maxlength="4" name="Applicants[0].DateOfBirth.Year" placeholder="YYYY" type="text" value="">
<span class="field-validation-valid" data-valmsg-for="Applicants[0].DateOfBirth.Year" data-valmsg-replace="true"></span>
<img class="split-date-calendar-button" src="./Create a case _ the Coventry for intermediaries 3_files/CalendarIcon.png" alt="Show calendar">
<span class="field-validation-valid" data-valmsg-for="Applicants[0].DateOfBirth" data-valmsg-replace="true"></span>
</div>
</div>
Second website:
<div class="sel formItemContainer" f-class="EntryRow" f-hasreadonlyrulebeenappliedbefore="True" f-isreadonly="False">
<div class="emc" id="Entry.Customer.C1.BirthDateEmc" f-hasreadonlyrulebeenappliedbefore="True" f-isreadonly="False"></div>
<label class="sc" for="Entry.Customer.C1.BirthDate" f-hasreadonlyrulebeenappliedbefore="True" f-isreadonly="False">Date of birth</label>
<div class="si" f-hasreadonlyrulebeenappliedbefore="True" f-isreadonly="False">
<span f-class="PatternEntry" f-iscustom="True" f-valuetype="Date" f-minimumlength="3" f-validationname="date of birth" f-isrequired="True" f-isrequiredrule="System.True" f-isreadonlyrule="Data.Customers.C1.AuthenticatedOrPersonalDetailsReadOnly" id="Entry.Customer.C1.BirthDate" value="23/02/1990" f-entryname="Customer.C1.BirthDate" f-submitsdata="True" class="date" xmlns:msxsl="urn:schemas-microsoft-com:xslt" f-hasreadonlyrulebeenappliedbefore="True" f-isreadonly="False" f-readonlyruleresult="False">
<input type="text" f-class="PatternPart" f-iscustom="True" f-behaviours="formItemChanged" value="444" maxlength="2" size="2" f-parttype="substring" f-partstart="1" f-partlength="2" f-isreadonly="False" f-hasreadonlyrulebeenappliedbefore="True" c-originalvalue="23/02/1989">
<span f-class="PatternPart" f-parttype="static" f-value="/" f-hasreadonlyrulebeenappliedbefore="True" f-isreadonly="False">/</span>
<input type="text" f-class="PatternPart" f-iscustom="True" f-behaviours="formItemChanged" value="02" maxlength="2" size="2" f-parttype="substring" f-partstart="4" f-partlength="2" f-isreadonly="False" f-hasreadonlyrulebeenappliedbefore="True" c-originalvalue="23/02/1989">
<span f-class="PatternPart" f-parttype="static" f-value="/" f-hasreadonlyrulebeenappliedbefore="True" f-isreadonly="False">/</span>
<input type="text" f-class="PatternPart" f-iscustom="True" f-behaviours="formItemChanged" value="1989" maxlength="4" size="4" f-parttype="substring" f-partstart="7" f-partlength="4" f-isreadonly="False" f-hasreadonlyrulebeenappliedbefore="True" c-originalvalue="23/02/1989">
</span>
</div>
</div>
Let's use the parameters:
d = '01'
m = '02'
y = '1933'
ar = [d,m,y]
Then the potential
code will be:
l = 'Date of birth'
fls = browser.find_elements_by_xpath('//label[contains(.,"{}")]'.format(l))
if fls:
# loop through array <<ar>> and pass each value one by one
element = browser.find_elements_by_xpath('//*[@id="{}"]'.format(fls[0].get_attribute("for")))
if element:
containtInput = element[0].tag_name.lower() == "input"
if containtInput:
x = element[0].find_elements_by_xpath(./following::/input[0])
x.send_keys(value)
break
回答1:
I don't think it is a good idea to spend too much time on a generic solution when a page specific solution is easy when it is done well. If you are sure that all the pages you want to use use the DD/MM/YYYY format and all the fields are numeric then you may try something like this:
def fill_date(input_holder_div, data):
fields = input_holder_div.find_elements_by_xpath('./input[@type="text"]')
for i in range(3):
fields[-i-1].clear()
fields[-i-1].send_keys(data[-i-1])
my_date = [11, 11, 1980]
# fill_date(driver.find_element_by_xpath('//*[@f-class="EntryRow"]'), my_date)
# fill_date(driver.find_element_by_xpath('//*[@class="rhsForm"]'), my_date)
Calling the function with the proper parent and the data and the job is done. The -i-1
part is required to fill the last 3 input fields only and ignore the hidden input field if such element is present. But there are side effects.
What if the page does not contain a parent element which contains only these 3 input text fields. What if you encounter a page where the date format is YYYY/MM/DD or the months are in a select by name...
If you want to do very similar or the same workflow on two different layouts, then you probably should check out the page object approach. The concept is that you define in a class how to do things on a page and then just call the methods and they do what they meant to do.
Example:
class BasePage:
def __init__(self, driver):
self.driver = driver
@staticmethod
def _fill(input_element, data):
input_element.clear()
input_element.send_keys(data)
class FirstPage(BasePage):
def fill_day(self, data):
field = self.driver.find_element_by_xpath('//*[@class="split-date-day"]')
self._fill(field, data)
class SecondPage(BasePage):
def fill_day(self, data):
field = self.driver.find_element_by_xpath('//*[@id="Entry.Customer.C1.BirthDate"]/input')
self._fill(field, data)
You have FirstPage and SecondPage classes, which know how to do stuff, like how to fill the day input field. If you complete those two classes (and add the fill_month
and fill_year
methods) you can handle date fills with the same logic:
def fill_date(page, data):
page.fill_day(data[0])
page.fill_month(data[1])
page.fill_year(data[2])
first_page = FirstPage(driver)
second_page = SecondPage(driver)
if first_page_is_loaded:
fill_date(first_page, my_data)
else:
fill_date(second_page, my_data)
If the workflow of the pages is very similar, all you need to do is to use the right page object. You may handle minor differences by moving some logic to the page object, and use the same flow in the main logic.
来源:https://stackoverflow.com/questions/61501307/selelnium-python-date-of-birth-fields-by-using-label