Unable to Select Checkbox element Selenium Python

I’m trying to generate data from this website https://brainbooks.pk/newtest-system/free_mcqs.php . The form is dynamic. In the form there are four drop down menus. I’m able to select first 3 (Board, Class and Subject) then there is the unit drop down the last one. The one with the check box. I want to loop through and check them one by one. I’m unable to loop through unit element. Here’s my code:

import time
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select, WebDriverWait
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.get('https://brainbooks.pk/newtest-system/free_mcqs.php')
def get_options(driver, class_name):
    drop_down = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, class_name)))
    select = Select(drop_down)
    return select.options
board_option = driver.find_element_by_xpath('//*[@id="board_id"]/option[2]')
board_option.click()
time.sleep(0.5)
class_options = ['//*[@id="class_id"]/option[2]','//*[@id="class_id"]/option[3]','//*[@id="class_id"]/option[4]','//*[@id="class_id"]/option[5]']
for class_select in class_options:
    select_class = driver.find_element_by_xpath(class_select)
    print(select_class.text)
    select_class.click()
    if class_select == '//*[@id="class_id"]/option[2]':
        subject_options = get_options(driver, 'subject')
        for subj in subject_options[1:]:
            print(subj.text)
            subj.click()
            topic_options = get_options(driver, 'q_unit')
            time.sleep(1.5)
            for topic in topic_options:
                print(topic)
                topic.click()
                time.sleep(1.5)
                break
            break
        break
    break

EDIT 1
The break are temporary. If one loop execute successfully them I’m going to remove them and let the script run completely.
I tried to loop through check box using this code "//input[@type='checkbox'][@data-chid='" + i+ "']" and got this error TypeError: 'WebElement' object is not iterable

import time
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select, WebDriverWait
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.get('https://brainbooks.pk/newtest-system/free_mcqs.php')
def get_options(driver, class_name):
    drop_down = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, class_name)))
    select = Select(drop_down)
    return select.options
board_option = driver.find_element_by_xpath('//*[@id="board_id"]/option[2]')
board_option.click()
time.sleep(0.5)
class_options = ['//*[@id="class_id"]/option[2]','//*[@id="class_id"]/option[3]','//*[@id="class_id"]/option[4]','//*[@id="class_id"]/option[5]']
for class_select in class_options:
    select_class = driver.find_element_by_xpath(class_select)
    print(select_class.text)
    select_class.click()
    if class_select == '//*[@id="class_id"]/option[2]':
        subject_options = get_options(driver, 'subject')
        for subj in subject_options[1:]:
            print(subj.text)
            subj.click()
            topic_options = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'q_unit')))
            time.sleep(1.5)
            for topic in topic_options:
                select_topic = driver.find_element_by_xpath("//input[@type='checkbox'][@data-chid='" + topic + "']").click()
                print(select_topic.text)
                select_topic.click()
                time.sleep(1.5)
                break
            break
        break
    break

EDIT 2
It’s working perfectly fine for 1 iteration after that I got this error element not interactable

import time

import pyautogui
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select, WebDriverWait
from webdriver_manager.chrome import ChromeDriverManager

driver = webdriver.Chrome(ChromeDriverManager().install())
driver.get('https://brainbooks.pk/newtest-system/free_mcqs.php')
def get_options(driver, class_name):
    drop_down = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, class_name)))
    select = Select(drop_down)
    return select.options
board_option = driver.find_element_by_xpath('//*[@id="board_id"]/option[2]')
board_option.click()
time.sleep(0.5)
class_options = ['//*[@id="class_id"]/option[2]','//*[@id="class_id"]/option[3]','//*[@id="class_id"]/option[4]','//*[@id="class_id"]/option[5]']
for class_select in class_options:
    select_class = driver.find_element_by_xpath(class_select)
    select_class.click()
    time.sleep(2)
    if class_select == '//*[@id="class_id"]/option[2]':
        try:
            subject_options = get_options(driver, 'subject')
        except:
            select_class = driver.find_element_by_xpath('//*[@id="class_id"]/option[1]')
            select_class.click()
            select_class = driver.find_element_by_xpath(class_select)
            select_class.click()
            time.sleep(2)
        finally:
            subject_options = get_options(driver, 'subject')
        for subj in subject_options[1:]:
            print(subj.text)
            sub_name = subj.text
            subj.click()
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'mutliSelect')))

            units_container = driver.find_element_by_class_name("mutliSelect")
            topic_options = units_container.find_elements_by_xpath("//input[@type='checkbox']")
            time.sleep(1.5)
            for select_topic in topic_options:
                topic_name = select_topic.find_element_by_xpath("..")
                print(topic_name.text)
                select_topic.click()
                time.sleep(1.5)
                generate_button = (driver.find_element_by_xpath('/html/body/div[2]/div/div[2]/div[1]/div/div[1]/div/button[1]')).click()
                time.sleep(3)
                view_result_button = (driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div[1]/div/div[1]/div/button[3]')).click()
                #Saving Html Pages
                topic_title = topic_name.text
                topic_title = topic_title.replace(':', '-')
                topic_title = topic_title.replace('  ', ' ')
                save_file_name = sub_name+'-'+topic_title
                pyautogui.hotkey("ctrlleft", "s") # Saving File
                time.sleep(.5)
                pyautogui.press('f4') # Opening Address Bar
                time.sleep(.5)
                pyautogui.hotkey("ctrlleft", "a") # Selecting Previous Address
                time.sleep(.5)
                pyautogui.press('delete') # Removing Previous Address
                time.sleep(.5)
                pyautogui.write('C:\UsersAli AbdullahDownloadsBrainFreeMCQS') # Entering New Address
                time.sleep(.5)
                pyautogui.press('enter')
                time.sleep(.5)
                pyautogui.click(496,342)
                time.sleep(.5)
                pyautogui.hotkey("ctrlleft", "a") # Selecting File Name
                time.sleep(.5)
                pyautogui.press('delete') # Deleting Previous File Name
                time.sleep(.5)
                pyautogui.write(save_file_name) # Entering New File Name
                time.sleep(.5)
                pyautogui.click(448,451) # Clicking Save

EDIT 3
When I try to add pyguiauto code to save the complete webpage. The select_topic.click() Which I use for De Selecting Previous CheckBox does not work. Error: 'WebElement' object is not iterable

import time
import pyautogui
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select, WebDriverWait
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.get('https://brainbooks.pk/newtest-system/free_mcqs.php')
def get_options(driver, class_name):
    drop_down = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, class_name)))
    select = Select(drop_down)
    return select.options
board_option = driver.find_element_by_xpath('//*[@id="board_id"]/option[2]')
board_option.click()
time.sleep(0.5)
class_options = ['//*[@id="class_id"]/option[2]','//*[@id="class_id"]/option[3]','//*[@id="class_id"]/option[4]','//*[@id="class_id"]/option[5]']
for class_select in class_options:
    select_class = driver.find_element_by_xpath(class_select)
    print(select_class.text)
    select_class.click()
    time.sleep(3)
    if class_select == '//*[@id="class_id"]/option[2]':
        try:
            subject_options = get_options(driver, 'subject')
        except:
            select_class = driver.find_element_by_xpath('//*[@id="class_id"]/option[1]')
            select_class.click()
            select_class = driver.find_element_by_xpath(class_select)
            select_class.click()
            time.sleep(2)
        finally:
            subject_options = get_options(driver, 'subject')
        for subj in subject_options[1:]:
            print(subj.text)
            subj.click()
            time.sleep(3)
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'mutliSelect')))
            units_container = driver.find_element_by_class_name("mutliSelect")
            topic_options = units_container.find_elements_by_xpath("//input[@type='checkbox']")
            time.sleep(1.5)
            for select_topic in topic_options:
                topic_name = select_topic.find_element_by_xpath("..")
                print(topic_name.text)
                select_topic.click()
                time.sleep(1.5)
                generate_button = (driver.find_element_by_xpath('/html/body/div[2]/div/div[2]/div[1]/div/div[1]/div/button[1]')).click()
                time.sleep(3)
                select_topic.click() #De Selecting Previous CheckBox
                time.sleep(1)
                view_result_button = (driver.find_element_by_xpath('/html/body/div[3]/div/div[2]/div[1]/div/div[1]/div/button[3]')).click()

                # Saving Html Pages
                sub_name = subj.text
                topic_title = topic_name.text
                topic_title = topic_title.replace(':', '-')
                topic_title = topic_title.replace('  ', ' ')
                save_file_name = sub_name + '-' + topic_title
                time.sleep(1)
                pyautogui.hotkey("ctrlleft", "s")  # Saving File
                time.sleep(.5)
                pyautogui.press('f4')  # Opening Address Bar
                time.sleep(.5)
                pyautogui.hotkey("ctrlleft", "a")  # Selecting Previous Address
                time.sleep(.5)
                pyautogui.press('delete')  # Removing Previous Address
                time.sleep(.5)
                pyautogui.write('C:\UsersAli AbdullahDownloadsBrainFreeMCQS')  # Entering New Address
                time.sleep(.5)
                pyautogui.press('enter')
                time.sleep(.5)
                pyautogui.click(496, 342)
                time.sleep(.5)
                pyautogui.hotkey("ctrlleft", "a")  # Selecting File Name
                time.sleep(.5)
                pyautogui.press('delete')  # Deleting Previous File Name
                time.sleep(.5)
                pyautogui.write(save_file_name)  # Entering New File Name
                time.sleep(.5)
                pyautogui.click(448, 451)  # Clicking Save
                time.sleep(1.5)



try:
    if driver is not None:
        driver.quit()
except Exception as e:
    print(e)

Answer

The issue with the code was that sometimes when you click on Class, the Subject select does not shows up. So, you were trying to click on an element what was not there. You can also use “mutliSelect” as classname and use that class name to fetch all the checkboxes. This could be more robust instead of getting all the checkboxes. Since this will also help you to get the names of the subjects. I also added a try except block because when trying it manually, sometimes the Subject Select won’t appear after selecting class. So I figured a way around it.

import time
from selenium import webdriver
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import Select, WebDriverWait
driver = webdriver.Chrome(ChromeDriverManager().install())
driver.get('https://brainbooks.pk/newtest-system/free_mcqs.php')
def get_options(driver, class_name):
    drop_down = WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, class_name)))
    select = Select(drop_down)
    return select.options
board_option = driver.find_element_by_xpath('//*[@id="board_id"]/option[2]')
board_option.click()
time.sleep(0.5)
class_options = ['//*[@id="class_id"]/option[2]','//*[@id="class_id"]/option[3]','//*[@id="class_id"]/option[4]','//*[@id="class_id"]/option[5]']
for class_select in class_options:
    select_class = driver.find_element_by_xpath(class_select)
    print(select_class.text)
    select_class.click()
    time.sleep(3)
    if class_select == '//*[@id="class_id"]/option[2]':
        try:
            subject_options = get_options(driver, 'subject')
        except:
            select_class = driver.find_element_by_xpath('//*[@id="class_id"]/option[1]')
            select_class.click()
            select_class = driver.find_element_by_xpath(class_select)
            select_class.click()
            time.sleep(2)
        finally:
            subject_options = get_options(driver, 'subject')
        for subj in subject_options[1:]:
            print(subj.text)
            subj.click()
            time.sleep(3)
            WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.CLASS_NAME, 'mutliSelect')))
            units_container = driver.find_element_by_class_name("mutliSelect")
            topic_options = units_container.find_elements_by_xpath("//input[@type='checkbox']")
            time.sleep(1.5)
            for select_topic in topic_options:
                topic_name = select_topic.find_element_by_xpath("..")
                print(topic_name.text)
                select_topic.click()
                time.sleep(1.5)
                break
            break
        break
    break


try:
    if driver is not None:
        driver.quit()
except Exception as e:
    print(e)

If you use q_unit, it will also work the same way. Since you are directly getting element from WebDriverWait, and using ID, it assumes there is only one element with the ID as that is how WebriverWait returns elements. So instead of getting element from WebDriverWait, you need to use it like this:

WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'q_unit')))
topic_options = driver.find_elements_by_id("q_unit")

And to get names using this method, it will be similar to what we did above:

for select_topic in topic_options:
    topic_name = select_topic.find_element_by_xpath("..")
    print(topic_name.text)
    select_topic.click()
    time.sleep(1.5)

I am getting the parent element from input type since li contains the text and not input tag. So how you are getting the text will not work.

This code is working for all the subjects now. Please look at the output if that is what was needed. Output