Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
f69cb2e
add API GET-method
RozhQA Jun 24, 2025
2238532
add test
RozhQA Jun 24, 2025
1833918
changed method to use a stable locator
RozhQA Jun 24, 2025
9713d5e
remove unused import
RozhQA Jun 24, 2025
89af234
ref config
RozhQA Jun 24, 2025
96f650d
ref config
RozhQA Jun 24, 2025
43843dd
add builds_list_screenshot
RozhQA Jun 24, 2025
3e13b59
Merge branch 'main' into ir/add-get-json
RozhQA Jun 24, 2025
395e1a9
fix config xml trigger
RozhQA Jun 26, 2025
e638dd3
fix config xml trigger
RozhQA Jun 26, 2025
9ed05d7
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
df9e4cf
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
7b93204
Merge pull request #1 from CodeByRozh/ir/ref-config
RozhQA Jun 26, 2025
52ffe1c
Merge branch 'main' into ir/add-get-json
RozhQA Jun 26, 2025
7f1341f
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
605cdcf
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
35ac833
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
8a205e3
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
d7ba2c7
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
22fea3b
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
e6c49f7
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
35e9ebc
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
df0f034
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
e87b24b
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
d8da2bd
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
409c93c
Run tests from tests/api/tests_ui only
RozhQA Jun 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions .env.TEMPLATE

This file was deleted.

13 changes: 11 additions & 2 deletions .github/workflows/continuous_integration.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
name: Run the tests on pull requests

on:
push:
branches:
- main
pull_request:
branches:
- main
Expand Down Expand Up @@ -43,6 +46,9 @@ jobs:
python -m pip install --upgrade pip
pip install -r requirements.txt

- name: Install pytest-repeat
run: pip install pytest-repeat

- name: Install the code linting and formatting tool Ruff
run: pip install ruff

Expand All @@ -69,8 +75,11 @@ jobs:
- name: Wait for the Jenkins
run: ./.github/wait-for-jenkins.sh

- name: pytest run all tests
run: pytest --alluredir=build/allure-results
# - name: Run tests from tests/api/tests_ui/frees only
# run: pytest tests/api/tests_ui --alluredir=build/allure-results

- name: Run one test 10 times
run: pytest tests/api/tests_ui/freestyle/test_build_triggers.py --count=1 --alluredir=build/allure-results

- name: Get Allure history
uses: actions/checkout@v4.2.2
Expand Down
17 changes: 9 additions & 8 deletions pages/freestyle_project_page.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import time

import allure
import logging

Expand All @@ -18,7 +20,7 @@ class Locators:
CONFIGURE_MENU_ITEM = (By.LINK_TEXT, 'Configure')
DESCRIPTION = (By.ID, 'description')
MENU_ITEMS = (By.XPATH, '//div[@class="task "]')
BUILDS_LINK = (By.CSS_SELECTOR, "#jenkins-build-history>div>span~div a")
BUILDS_LINK = (By.CSS_SELECTOR, ".app-builds-container__item")

def __init__(self, driver, project_name, timeout=5):
super().__init__(driver, timeout=timeout)
Expand Down Expand Up @@ -65,12 +67,11 @@ def get_menu_items_texts(self):
return [item.text for item in self.wait_to_be_visible_all(self.Locators.MENU_ITEMS)]

@allure.step("Wait up to {timeout} seconds for the build to appear in the build history.")
def wait_for_build_execution(self, timeout):
with allure.step("Wait for 'Builds' link to be visible"):
build_link = self.wait_for_element(self.Locators.BUILDS_LINK, timeout)
if not build_link:
logger.error(f"'Builds' link was not found within {timeout} seconds.")
else:
logger.info(f"'Builds' link appeared within {timeout} seconds.")
def wait_for_build_execution(self, timeout=60):
logger.info("Waiting 60 seconds before counting builds...")
time.sleep(180)

builds = self.find_elements(*self.Locators.BUILDS_LINK)
count = len(builds)
logger.info(f"{count} build(s) appeared after {timeout} seconds.")
return self
5 changes: 2 additions & 3 deletions pages/main_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,9 @@ def go_to_the_folder_page(self, name):
return self.navigate_to(FolderPage, self.Locators.table_item_link(name), name)

@allure.step("Go to the Freestyle project page by clicking project link.")
def go_to_freestyle_project_page(self, project_name):
def go_to_freestyle_project_page(self, name):
from pages.freestyle_project_page import FreestyleProjectPage
self.wait_to_be_clickable(self.Locators.PROJECT_BUTTON).click()
return FreestyleProjectPage(self.driver, project_name).wait_for_url()
return self.navigate_to(FreestyleProjectPage, self.Locators.table_item_link(name), name)

@allure.step("Expand build queue info block if it is collapsed.")
def show_build_queue_info_block(self):
Expand Down
20 changes: 20 additions & 0 deletions tests/api/steps/jenkins_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,23 @@ def post_create_item_in_folder(self, folder_name: str, project_name: str, config
f"Endpoint: {endpoint}\n"
f"Status: {getattr(response, 'status_code', 'N/A')}"
)

@allure.step("GET /job/{project_name}/api/json")
def get_job_config_json(self, project_name: str, params: dict = None) -> dict:
endpoint = f"job/{project_name}/api/json"
query_params = {"pretty": "true", **(params or {})}
response = self.client.get(endpoint, params=query_params)

if response and response.ok:
allure.attach(response.text, name="JSON data", attachment_type=allure.attachment_type.JSON)
return response.json()

else:
msg = response.text if response else "No response"
allure.attach(msg, name=f"[Failed]{project_name}", attachment_type=allure.attachment_type.TEXT)
raise RuntimeError(
f"Failed to get config for job '{project_name}'.\n"
f"Endpoint: /{endpoint}\n"
f"Params: {params}\n"
f"Status: {response.status_code} - {response.reason}"
)
8 changes: 8 additions & 0 deletions tests/api/support/jenkins_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ def post_xml(self, endpoint: str, xml_data: str):
return response

return response

def get(self, endpoint: str, params: dict = None) -> requests.Response:
headers = {'Content-Type': 'application/json'}
headers.update(self.crumb_headers)
url = f"{self.BASE_URL}/{endpoint}"
response = self.session.get(url, headers=headers, params=params)
logger.info(f"[GET] /{endpoint} - Status: {response.status_code} - {response.reason}")
return response
10 changes: 10 additions & 0 deletions tests/api/tests_ui/freestyle/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,13 @@ def create_empty_job_with_api() -> Dict[str, Any]:
"token": token,
"crumb_headers": crumb_headers
}


@pytest.fixture(scope="function")
def create_freestyle_scheduled_project_by_xml_via_api(jenkins_steps, main_page):
project_name, timer, timeout, config_xml = Data.get_freestyle_scheduled_every_minute_data()
jenkins_steps.post_create_item(project_name, config_xml)
main_page.driver.refresh()
main_page.go_to_freestyle_project_page(project_name).wait_for_build_execution(timeout)
json_data = jenkins_steps.get_job_config_json(project_name)
return project_name, json_data
16 changes: 11 additions & 5 deletions tests/api/tests_ui/freestyle/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ class CronTimer:
every_minute: dict[str, str | int] = {
"timer": "*/1 * * * *",
"schedule": "1 min",
"timeout": 60
"timeout": 180
}
every_two_minutes: dict[str, str | int] = {
"timer": "H/2 * * * *",
Expand All @@ -31,12 +31,18 @@ def get_freestyle_scheduled_xml(cls, description: str, timer: str) -> str:
<blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
<blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
<triggers>
<hudson.triggers.TimerTrigger>
<spec>{timer}</spec>
</hudson.triggers.TimerTrigger>
<hudson.triggers.TimerTrigger>
<spec>{timer}</spec>
</hudson.triggers.TimerTrigger>
</triggers>
<concurrentBuild>false</concurrentBuild>
<builders/>
<builders>
<hudson.tasks.Shell>
<command>
sleep 35
</command>
</hudson.tasks.Shell>
</builders>
<publishers/>
<buildWrappers/>
</project>
Expand Down
24 changes: 22 additions & 2 deletions tests/api/tests_ui/freestyle/test_build_triggers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import allure
import pytest


@allure.epic("Freestyle Project Configuration")
Expand All @@ -9,7 +8,6 @@
"and that the build runs according to the specified schedule.")
@allure.testcase("https://github.com/RedRoverSchool/JenkinsQA_Python_2025_spring/issues/643", "TC_02.004.002")
@allure.link("https://github.com/RedRoverSchool/JenkinsQA_Python_2025_spring/issues/643", name="Github issue")
@pytest.mark.xfail(reason="May fail due to non-reproducible concurrent builds locally.")
def test_user_can_trigger_build_periodically(create_freestyle_project_scheduled_every_minute_by_api, main_page):
project_name, timeout = create_freestyle_project_scheduled_every_minute_by_api

Expand All @@ -20,9 +18,31 @@ def test_user_can_trigger_build_periodically(create_freestyle_project_scheduled_
.go_to_build_history_page()\
.get_builds_list()

with allure.step("Attach screenshot before asserting number of builds."):
screenshot = main_page.driver.get_screenshot_as_png()
allure.attach(screenshot, name="builds_list_screenshot", attachment_type=allure.attachment_type.PNG)
with allure.step("Assert that only one build is displayed in the list."):
assert len(builds) == 1, f"Expected 1 build, found {len(builds)}"
with allure.step(f"Assert that the project name of the displayed build is \"{project_name}\"."):
assert builds[0].split("\n")[0] == project_name, f"No build entry found for '{project_name}'"
with allure.step("Assert that the build has number \"#1\"."):
assert builds[0].split("\n")[1] == "#1", "Build #1 not found."


def test_freestyle_project_ui_fields_match_api_json(create_freestyle_scheduled_project_by_xml_via_api, main_page):
project_name, json_data = create_freestyle_scheduled_project_by_xml_via_api
page = main_page.go_to_freestyle_project_page(project_name)
assert project_name in json_data.get("url")
assert page.get_h1_value() == json_data.get("displayName")
assert page.get_description() == json_data.get("description")

builds_ui = page.header.go_to_the_main_page() \
.go_to_build_history_page() \
.get_builds_list()

first_build_ui_number = builds_ui[0].split("\n")[1]
first_build_json_number = f"#{json_data['builds'][0]['number']}"
with allure.step("Attach screenshot before asserting number of builds."):
screenshot = main_page.driver.get_screenshot_as_png()
allure.attach(screenshot, name="builds_list_screenshot", attachment_type=allure.attachment_type.PNG)
assert first_build_ui_number == first_build_json_number == "#1"
Loading