Skip to content

Commit 3b575ef

Browse files
author
Saqib
committed
Add collaborative creation feature for individual and organization users
Implements end-to-end collaborative creation flow with page objects, locators, test data, and smoke tests for both individual and organization user types.
1 parent 745cc75 commit 3b575ef

12 files changed

Lines changed: 1342 additions & 0 deletions

conftest.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,18 @@ def sample_profile_image_path():
181181
raise FileNotFoundError(f"Expected sample_profile_image.png at {profile_image_path}")
182182
return profile_image_path
183183

184+
@pytest.fixture()
185+
def sample_cover_image_path():
186+
"""
187+
Returns an absolute path to a cover image under tests/data/
188+
for cover image uploads in collaborative creation.
189+
"""
190+
here = os.path.dirname(__file__)
191+
cover_image_path = os.path.abspath(os.path.join(here, "tests", "data", "sample_profile_image.png"))
192+
if not os.path.isfile(cover_image_path):
193+
raise FileNotFoundError(f"Expected sample_profile_image.png at {cover_image_path}")
194+
return cover_image_path
195+
184196
@pytest.fixture(scope="session")
185197
def test_credentials():
186198
"""
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# locators/provider/collaboratives_list_page_locators.py
2+
from selenium.webdriver.common.by import By
3+
4+
class CollaborativesListPageLocators:
5+
ADD_NEW_COLLABORATIVE_BUTTON = (By.XPATH, "//div[@class='flex items-center gap-3']//span[@class='Button-module_removeUnderline__dq0ct'][normalize-space()='Add New Collaborative']")
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# locators/provider/create_collaborative_locators.py
2+
3+
from selenium.webdriver.common.by import By
4+
5+
class CreateCollaborativeLocators:
6+
"""
7+
All locators for the CreateCollaborativePage.
8+
Keep things as simple constants so that the POM methods can refer to them by name.
9+
"""
10+
11+
# ─── "Collaborative Details" Tab and Fields ─────────────────────────────────────
12+
DETAILS_TAB = (By.XPATH, "//button[normalize-space()='Collaborative Details']")
13+
14+
# Collaborative Name - Edit button and save functionality
15+
COLLABORATIVE_NAME_EDIT_BUTTON = (By.XPATH, "//div[@class=' pl-2']//button[@type='button']")
16+
# Try multiple strategies to find the name input
17+
COLLABORATIVE_NAME_INPUT = (By.XPATH, "//label[contains(text(),'COLLABORATIVE NAME')]/following::input[1]")
18+
COLLABORATIVE_NAME_INPUT_ALT = (By.CSS_SELECTOR, "input[type='text']")
19+
COLLABORATIVE_NAME_INPUT_ALT2 = (By.ID, "collaborativeName")
20+
COLLABORATIVE_NAME_SAVE_BUTTON = (By.XPATH, "//button[normalize-space()='Save']")
21+
# Locator for the displayed collaborative name (after save)
22+
COLLABORATIVE_NAME_DISPLAY = (By.XPATH, "//span[contains(text(),'COLLABORATIVE NAME')]/following-sibling::*[1]")
23+
COLLABORATIVE_NAME_DISPLAY_ALT = (By.XPATH, "//div[contains(@class,'flex')]//div[@class=' pl-2']")
24+
COLLABORATIVE_NAME_DISPLAY_ALT2 = (By.XPATH, "//label[contains(text(),'COLLABORATIVE NAME')]/following::div[1]")
25+
26+
# Summary field (rich text editor)
27+
COLLABORATIVE_SUMMARY_LABEL = (By.XPATH, "//label[normalize-space()='Summary *']")
28+
COLLABORATIVE_SUMMARY_INPUT = (By.XPATH, "//div[contains(@class, 'ql-editor') and @contenteditable='true']")
29+
30+
# Platform URL
31+
PLATFORM_URL_INPUT = (By.XPATH, "//input[@name='platformUrl']")
32+
33+
# SDG Goals (try both singular and plural)
34+
SDG_GOALS_CONTAINER = (By.XPATH, "//label[normalize-space()='SDG Goals *']/following::input[1]")
35+
SDG_GOALS_CONTAINER_ALT = (By.XPATH, "//label[normalize-space()='SDG Goal *']/following::input[1]")
36+
SDG_GOALS_CONTAINER_ALT2 = (By.XPATH, "//label[contains(text(),'SDG')]/following::input[1]")
37+
SDG_GOALS_OPTION = "//div[@role='option' and normalize-space(.)='{value}']"
38+
39+
# Tags
40+
TAGS_INPUT = (By.XPATH, "//label[normalize-space()='Tags']/following::input[@role='combobox'][1]")
41+
TAG_DROPDOWN_ITEM = "//div[@role='option' and normalize-space(.)='{value}']"
42+
43+
# Sectors
44+
SECTOR_INPUT = (By.XPATH, "//label[normalize-space()='Sectors *']/following::input[@role='combobox'][1]")
45+
SECTOR_DROPDOWN_ITEM = "//div[@role='option' and normalize-space(.)='{value}']"
46+
47+
# Geography
48+
GEOGRAPHY_CONTAINER = (By.XPATH, "//label[normalize-space()='Geographies']/following::input[1]")
49+
GEO_OPTION = "//div[@role='option' and normalize-space(.)='{value}']"
50+
51+
# Dates
52+
STARTED_ON_INPUT = (By.XPATH, "//input[@type='date' and @name='startedOn']")
53+
COMPLETED_ON_INPUT = (By.XPATH, "//input[@type='date' and @name='completedOn']")
54+
55+
# Logo Upload
56+
LOGO_UPLOAD_INPUT = (By.XPATH, "//label[normalize-space()='Logo *']//following::div[@class='FileUpload-module_Action__Hg0nE'][1]")
57+
58+
# Cover Image Upload
59+
COVER_IMAGE_UPLOAD_INPUT = (By.XPATH, "//label[normalize-space()='Cover Image']//following::div[@class='FileUpload-module_Action__Hg0nE'][1]")
60+
61+
# ─── Fields Used for Value Retrieval / Assertions ──────────────────────────
62+
SUMMARY_INPUT = (By.XPATH, "//div[contains(@class, 'ql-editor') and @contenteditable='true']")
63+
STARTED_ON_VALUE_INPUT = (By.XPATH, "//input[@type='date' and @name='startedOn']")
64+
COMPLETED_ON_VALUE_INPUT = (By.XPATH, "//input[@type='date' and @name='completedOn']")
65+
66+
# ─── Tags / Sectors / SDG Goals / Geography Chips (Assertions) ─────────────────────────
67+
SELECTED_TAGS = "//div[contains(@class,'Input-module_tags')]//span[contains(@class,'Tag-module_TagText')]"
68+
SELECTED_SECTORS = "//div[contains(@class,'Input-module_tags')]//span[contains(@class,'Tag-module_TagText')]"
69+
SELECTED_GEOGRAPHY = "//label[normalize-space(text())='Geographies']/following::div[contains(@class,'Input-module_tags')][1]//span[contains(@class,'Tag-module_TagText')]"
70+
SELECTED_SDG_GOALS = "//div[contains(@class,'Input-module_tags')]//span[contains(@class,'Tag-module_TagText')]"
71+
72+
# ─── Logo & Cover Image Preview (Post-upload Check) ──────────────────────────────────────
73+
LOGO_PREVIEW = (By.CSS_SELECTOR, ".uploaded-logo-preview")
74+
COVER_IMAGE_PREVIEW = (By.CSS_SELECTOR, ".uploaded-cover-preview")
75+
76+
# ─── Navigation Buttons ─────────────────────────────────────────────────────────────
77+
NEXT_BUTTON = (By.XPATH, "//button[contains(text(),'Next')]")
78+
PREVIOUS_BUTTON = (By.XPATH, "//button[contains(text(),'Previous')]")
79+
80+
# ─── "Datasets" Tab (if applicable) ────────────────────────────────────────────────────────
81+
DATASETS_TAB = (By.XPATH, "//button[normalize-space()='Datasets']")
82+
FIRST_DATASET_CHECKBOX = (By.XPATH, "//tbody/tr[1]//button[@role='checkbox']")
83+
SELECTED_DATASET_CHECKBOX = (By.XPATH, "//tbody/tr[1]//button[@data-state='checked' and @aria-checked='true']")
84+
SUBMIT_DATASETS_BUTTON = (By.XPATH, "//button[normalize-space()='Submit']")
85+
86+
# ─── "Use Cases" Tab (if applicable) ────────────────────────────────────────────────────────
87+
USECASES_TAB = (By.XPATH, "//button[normalize-space()='Use Cases']")
88+
FIRST_USECASE_CHECKBOX = (By.XPATH, "//tbody/tr[1]//button[@role='checkbox']")
89+
SELECTED_USECASE_CHECKBOX = (By.XPATH, "//tbody/tr[1]//button[@data-state='checked' and @aria-checked='true']")
90+
SUBMIT_USECASES_BUTTON = (By.XPATH, "//button[normalize-space()='Submit']")
91+
92+
# ─── "Contributors" Tab ────────────────────────────────────────────────────
93+
CONTRIBUTORS_TAB = (By.XPATH, "//button[normalize-space()='Contributors']")
94+
CONTRIBUTORS_INPUT = (By.XPATH, "//input[@placeholder='Add Contributors']")
95+
SUPPORTERS_INPUT = (By.XPATH, "//input[@placeholder='Add Supporters']")
96+
PARTNERS_INPUT = (By.XPATH, "//input[@placeholder='Add Partners']")
97+
98+
# ─── Contributors/Sponsors List Items ──────────────────────────────────────
99+
CONTRIBUTORS_LIST_ITEMS = (
100+
By.XPATH,
101+
"//div[contains(@class,'flex') and contains(@class,'gap-2')]/span[contains(@class, 'Text-module_bodyMd')]"
102+
)
103+
SUPPORTERS_LIST_ITEMS = (By.CSS_SELECTOR, ".supporters-list-item")
104+
PARTNERS_LIST_ITEMS = (By.CSS_SELECTOR, ".partners-list-item")
105+
106+
# ─── "Publish" Tab ─────────────────────────────────────────────────────────
107+
PUBLISH_TAB = (By.XPATH, "//button[normalize-space()='Publish']")
108+
PUBLISH_BUTTON = (
109+
By.XPATH,
110+
"//button[normalize-space()='Publish' and contains(@class,'Button-module_Button')]"
111+
)
112+
113+
PUBLISHED_MARKER = (By.XPATH, "//div[contains(text(),'Collaborative Published Successfully')]")
114+
115+
# ─── Generic Dropdown Option Pattern ───────────────────────────────────────
116+
OPTION_BY_VISIBLE_TEXT = "//div[@role='option' and normalize-space(text())='{text}']"

locators/provider/my_dashboard_locators.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class MyDashboardLocators:
1313
# (2) Sidebar navigation links
1414
SIDEBAR_DATASETS = (By.XPATH, '//span[normalize-space()="Datasets"]')
1515
USECASES_NAV_LINK = (By.XPATH, '//span[normalize-space()="UseCases"]')
16+
COLLABORATIVES_NAV_LINK = (By.XPATH, '//span[normalize-space()="Collaboratives"]')
1617
PROFILE_NAV_LINK = (By.XPATH, '//span[normalize-space()="Profile"]')
1718

1819
# (3) Inside the "Datasets" panel, the "Drafts" tab is visible by default.

locators/provider/org_locators.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,5 @@ class OrgLocators:
2727
DRAFTS_TAB = MyDashboardLocators.DRAFTS_TAB
2828
ADD_NEW_DATASET_BTN = MyDashboardLocators.ADD_NEW_DATASET_BTN
2929
USECASES_NAV_LINK = MyDashboardLocators.USECASES_NAV_LINK
30+
COLLABORATIVES_NAV_LINK = MyDashboardLocators.COLLABORATIVES_NAV_LINK
3031
PROFILE_NAV_LINK = MyDashboardLocators.PROFILE_NAV_LINK
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# pages/provider/collaboratives_list_page.py
2+
from selenium.webdriver.support.ui import WebDriverWait
3+
from selenium.webdriver.support import expected_conditions as EC
4+
from selenium.webdriver.common.by import By
5+
from pages.base_page import BasePage
6+
from pages.provider.create_collaborative_page import CreateCollaborativePage
7+
from locators.provider.collaboratives_list_page_locators import CollaborativesListPageLocators
8+
from locators.provider.create_collaborative_locators import CreateCollaborativeLocators
9+
10+
11+
class CollaborativesListPage(BasePage):
12+
13+
def __init__(self, driver):
14+
super().__init__(driver) # Initialize BasePage with self.wait
15+
16+
def is_loaded(self):
17+
return self.wait_with_timeout(10).until(
18+
EC.visibility_of_element_located(CollaborativesListPageLocators.ADD_NEW_COLLABORATIVE_BUTTON)
19+
)
20+
21+
def click_add_new_collaborative(self):
22+
"""
23+
Click the "Add New Collaborative" button and wait for the create collaborative form to load.
24+
25+
Returns:
26+
CreateCollaborativePage instance after the form has loaded
27+
"""
28+
import time
29+
30+
# Wait for the button to be clickable before clicking
31+
button = self.wait_with_timeout(10).until(
32+
EC.element_to_be_clickable(CollaborativesListPageLocators.ADD_NEW_COLLABORATIVE_BUTTON),
33+
message="Timed out waiting for 'Add New Collaborative' button to be clickable"
34+
)
35+
button.click()
36+
37+
# Give the modal/form time to appear
38+
time.sleep(2)
39+
40+
# Wait for the Collaborative creation form to load by checking for the summary textarea
41+
self.wait_with_timeout(15).until(
42+
EC.visibility_of_element_located(CreateCollaborativeLocators.COLLABORATIVE_SUMMARY_INPUT),
43+
message="Timed out waiting for Collaborative creation form to load"
44+
)
45+
46+
return CreateCollaborativePage(self.driver)

0 commit comments

Comments
 (0)