Skip to content

Commit 446dec2

Browse files
committed
Update project code, add training files
1 parent ee77eb2 commit 446dec2

File tree

10 files changed

+182
-89
lines changed

10 files changed

+182
-89
lines changed

python-selenium/src/bandcamp/app/player.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
from selenium.webdriver import Firefox
22
from selenium.webdriver.firefox.options import Options
33

4-
from bandcamp.web.element import TrackElement
54
from bandcamp.web.page import DiscoverPage
65

76
BANDCAMP_DISCOVER_URL = "https://bandcamp.com/discover/"
@@ -14,9 +13,7 @@ def __init__(self) -> None:
1413
self._driver = self._set_up_driver()
1514
self.page = DiscoverPage(self._driver)
1615
self.tracklist = self.page.discover_tracklist
17-
self._current_track = TrackElement(
18-
self.tracklist.available_tracks[0], self._driver
19-
)
16+
self._current_track = self.tracklist.available_tracks[0]
2017

2118
def __enter__(self):
2219
return self
@@ -28,10 +25,7 @@ def __exit__(self, exc_type, exc_value, exc_tb):
2825
def play(self, track_number=None):
2926
"""Play the first track, or one of the available numbered tracks."""
3027
if track_number:
31-
self._current_track = TrackElement(
32-
self.tracklist.available_tracks[track_number - 1],
33-
self._driver,
34-
)
28+
self._current_track = self.tracklist.available_tracks[track_number - 1]
3529
self._current_track.play()
3630

3731
def pause(self):

python-selenium/src/bandcamp/app/tui.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ def interact():
88
"""Control the player through user interactions."""
99
with Player() as player:
1010
while True:
11-
print(
12-
"\nType: play [<track number>] | pause | tracks | more | exit"
13-
)
11+
print("\nType: play [<track number>] | pause | tracks | more | exit")
1412
match input("> ").strip().lower().split():
1513
case ["play"]:
1614
play(player)
@@ -24,12 +22,8 @@ def interact():
2422
pause(player)
2523
case ["tracks"]:
2624
display_tracks(player)
27-
case ["more"] if len(
28-
player.tracklist.available_tracks
29-
) >= MAX_TRACKS:
30-
print(
31-
"Can't load more tracks. Pick one from the track list."
32-
)
25+
case ["more"] if len(player.tracklist.available_tracks) >= MAX_TRACKS:
26+
print("Can't load more tracks. Pick one from the track list.")
3327
case ["more"]:
3428
player.tracklist.load_more()
3529
display_tracks(player)
@@ -62,17 +56,14 @@ def display_tracks(player):
6256
header = f"{'#':<5} {'Album':<{CW}} {'Artist':<{CW}} {'Genre':<{CW}}"
6357
print(header)
6458
print("-" * 100)
65-
for track_number, track in enumerate(
59+
for track_number, track_element in enumerate(
6660
player.tracklist.available_tracks, start=1
6761
):
68-
if track.text:
69-
album, artist, *genre = track.text.split("\n")
70-
album = _truncate(album, CW)
71-
artist = _truncate(artist, CW)
72-
genre = _truncate(genre[0], CW) if genre else ""
73-
print(
74-
f"{track_number:<5} {album:<{CW}} {artist:<{CW}} {genre:<{CW}}"
75-
)
62+
track = track_element._get_track_info()
63+
album = _truncate(track.album, CW)
64+
artist = _truncate(track.artist, CW)
65+
genre = _truncate(track.genre, CW)
66+
print(f"{track_number:<5} {album:<{CW}} {artist:<{CW}} {genre:<{CW}}")
7667

7768

7869
def _truncate(text, width):

python-selenium/src/bandcamp/web/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from selenium.webdriver.support.wait import WebDriverWait
66

77
MAX_WAIT_SECONDS = 10.0
8-
DEFAULT_WINDOW_SIZE = (1920, 3000) # Shows 60 tracks
8+
DEFAULT_WINDOW_SIZE = (1920, 3000)
99

1010

1111
@dataclass

python-selenium/src/bandcamp/web/element.py

Lines changed: 47 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,52 @@
44
from selenium.webdriver.support import expected_conditions as EC
55

66
from bandcamp.web.base import Track, WebComponent
7-
from bandcamp.web.locators import DiscoverPageLocator, TrackLocator
7+
from bandcamp.web.locators import TrackListLocator, TrackLocator
8+
9+
10+
class TrackListElement(WebComponent):
11+
"""Model the track list on Bandcamp's Discover page."""
12+
13+
def __init__(self, parent: WebElement, driver: WebDriver = None) -> None:
14+
super().__init__(parent, driver)
15+
self.available_tracks = self._get_available_tracks()
16+
17+
def load_more(self) -> None:
18+
"""Load additional tracks."""
19+
view_more_button = self._driver.find_element(
20+
*TrackListLocator.PAGINATION_BUTTON
21+
)
22+
view_more_button.click()
23+
# The button is disabled until all new tracks are loaded.
24+
self._wait.until(EC.element_to_be_clickable(TrackListLocator.PAGINATION_BUTTON))
25+
self.available_tracks = self._get_available_tracks()
26+
27+
def _get_available_tracks(self) -> list:
28+
"""Find all currently available tracks."""
29+
self._wait.until(
30+
self._track_text_loaded,
31+
message="Timeout waiting for track text to load",
32+
)
33+
34+
all_tracks = self._driver.find_elements(*TrackListLocator.ITEM)
35+
36+
# Filter tracks that are displayed and have text.
37+
return [
38+
TrackElement(track, self._driver)
39+
for track in all_tracks
40+
if track.is_displayed() and track.text.strip()
41+
]
42+
43+
def _track_text_loaded(self, driver):
44+
"""Check if the track text has loaded."""
45+
return any(
46+
e.is_displayed() and e.text.strip()
47+
for e in driver.find_elements(*TrackListLocator.ITEM)
48+
)
849

950

1051
class TrackElement(WebComponent):
11-
"""Model a playable track in Bandcamp's Discover section."""
52+
"""Model a playable track on Bandcamp's Discover page."""
1253

1354
def play(self) -> None:
1455
"""Play the track."""
@@ -24,11 +65,12 @@ def pause(self) -> None:
2465
def is_playing(self) -> bool:
2566
return "Pause" in self._get_play_button().get_attribute("aria-label")
2667

68+
def _get_play_button(self):
69+
return self._parent.find_element(*TrackLocator.PLAY_BUTTON)
70+
2771
def _get_track_info(self) -> Track:
2872
"""Create a representation of the track's relevant information."""
29-
full_url = self._parent.find_element(*TrackLocator.URL).get_attribute(
30-
"href"
31-
)
73+
full_url = self._parent.find_element(*TrackLocator.URL).get_attribute("href")
3274
# Cut off the referrer query parameter
3375
clean_url = full_url.split("?")[0] if full_url else ""
3476
# Some tracks don't have a genre
@@ -42,53 +84,3 @@ def _get_track_info(self) -> Track:
4284
genre=genre,
4385
url=clean_url,
4486
)
45-
46-
def _get_play_button(self):
47-
return self._parent.find_element(*TrackLocator.PLAY_BUTTON)
48-
49-
50-
class DiscoverTrackList(WebComponent):
51-
"""Model the track list in Bandcamp's Discover section."""
52-
53-
def __init__(self, parent: WebElement, driver: WebDriver = None) -> None:
54-
super().__init__(parent, driver)
55-
self.available_tracks = self._get_available_tracks()
56-
57-
def load_more(self) -> None:
58-
"""Load additional tracks in the Discover section."""
59-
view_more_button = self._driver.find_element(
60-
*DiscoverPageLocator.PAGINATION_BUTTON
61-
)
62-
view_more_button.click()
63-
# The button is disabled until all new tracks are loaded.
64-
self._wait.until(
65-
EC.element_to_be_clickable(DiscoverPageLocator.PAGINATION_BUTTON)
66-
)
67-
self.available_tracks = self._get_available_tracks()
68-
69-
def _get_available_tracks(self) -> list:
70-
"""Find all currently available tracks in the Discover section."""
71-
self._wait.until(
72-
self._track_text_loaded,
73-
message="Timeout waiting for track text to load",
74-
)
75-
76-
all_items = self._driver.find_elements(*DiscoverPageLocator.ITEM)
77-
all_tracks = []
78-
for item in all_items:
79-
if item.find_element(*TrackLocator.PLAY_BUTTON):
80-
all_tracks.append(item)
81-
82-
# Filter tracks that are displayed and have text.
83-
return [
84-
track
85-
for track in all_tracks
86-
if track.is_displayed() and track.text.strip()
87-
]
88-
89-
def _track_text_loaded(self, driver):
90-
"""Check if the track text has loaded."""
91-
return any(
92-
e.is_displayed() and e.text.strip()
93-
for e in driver.find_elements(*DiscoverPageLocator.ITEM)
94-
)

python-selenium/src/bandcamp/web/locators.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33

44
class DiscoverPageLocator:
55
DISCOVER_RESULTS = (By.CLASS_NAME, "results-grid")
6-
ITEM = (By.CLASS_NAME, "results-grid-item")
7-
PAGINATION_BUTTON = (By.ID, "view-more")
86
COOKIE_ACCEPT_NECESSARY = (
97
By.CSS_SELECTOR,
108
"#cookie-control-dialog button.g-button.outline",
119
)
1210

1311

12+
class TrackListLocator:
13+
ITEM = (By.CLASS_NAME, "results-grid-item")
14+
PAGINATION_BUTTON = (By.ID, "view-more")
15+
16+
1417
class TrackLocator:
1518
PLAY_BUTTON = (By.CSS_SELECTOR, "button.play-pause-button")
1619
URL = (By.CSS_SELECTOR, "div.meta p a")
Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
from selenium.common.exceptions import NoSuchElementException
12
from selenium.webdriver.remote.webdriver import WebDriver
23

34
from bandcamp.web.base import WebPage
4-
from bandcamp.web.element import DiscoverTrackList
5+
from bandcamp.web.element import TrackListElement
56
from bandcamp.web.locators import DiscoverPageLocator
67

78

@@ -11,13 +12,16 @@ class DiscoverPage(WebPage):
1112
def __init__(self, driver: WebDriver) -> None:
1213
super().__init__(driver)
1314
self._accept_cookie_consent()
14-
self.discover_tracklist = DiscoverTrackList(
15+
self.discover_tracklist = TrackListElement(
1516
self._driver.find_element(*DiscoverPageLocator.DISCOVER_RESULTS),
1617
self._driver,
1718
)
1819

1920
def _accept_cookie_consent(self) -> None:
2021
"""Accept the necessary cookie consent."""
21-
self._driver.find_element(
22-
*DiscoverPageLocator.COOKIE_ACCEPT_NECESSARY
23-
).click()
22+
try:
23+
self._driver.find_element(
24+
*DiscoverPageLocator.COOKIE_ACCEPT_NECESSARY
25+
).click()
26+
except NoSuchElementException:
27+
pass
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import time
2+
3+
from selenium import webdriver
4+
from selenium.common.exceptions import NoSuchElementException
5+
from selenium.webdriver.common.by import By
6+
7+
driver = webdriver.Firefox() # Run in normal mode
8+
driver.get("https://bandcamp.com/discover/")
9+
10+
# Accept cookies, if required
11+
try:
12+
cookie_accept_button = driver.find_element(
13+
By.CSS_SELECTOR,
14+
"#cookie-control-dialog button.g-button.outline",
15+
)
16+
cookie_accept_button.click()
17+
except NoSuchElementException:
18+
pass
19+
20+
time.sleep(0.5)
21+
22+
search = driver.find_element(By.CLASS_NAME, "site-search-form")
23+
search_field = search.find_element(By.TAG_NAME, "input")
24+
search_field.send_keys("selenium")
25+
search_field.submit()
26+
27+
time.sleep(5)
28+
29+
driver.quit()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import time
2+
3+
from selenium import webdriver
4+
from selenium.webdriver.common.by import By
5+
from selenium.webdriver.firefox.options import Options
6+
7+
options = Options()
8+
options.add_argument("--headless")
9+
driver = webdriver.Firefox(options=options)
10+
driver.get("https://bandcamp.com/discover/")
11+
12+
tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
13+
print(len(tracks))
14+
15+
pagination_button = driver.find_element(By.ID, "view-more")
16+
pagination_button.click()
17+
18+
time.sleep(0.5)
19+
20+
tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
21+
print(len(tracks))
22+
23+
driver.quit()
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from selenium import webdriver
2+
from selenium.webdriver.common.by import By
3+
from selenium.webdriver.firefox.options import Options
4+
5+
options = Options()
6+
options.add_argument("--headless")
7+
driver = webdriver.Firefox(options=options)
8+
9+
driver.get("https://bandcamp.com/discover/")
10+
print(driver.title)
11+
12+
pagination_button = driver.find_element(By.ID, "view-more")
13+
print(pagination_button.accessible_name)
14+
15+
tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
16+
print(len(tracks))
17+
print(tracks[0].text)
18+
19+
track_1 = tracks[0]
20+
album = track_1.find_element(By.CSS_SELECTOR, "div.meta a strong")
21+
print(album.text)
22+
23+
driver.quit()
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from selenium import webdriver
2+
from selenium.common.exceptions import NoSuchElementException
3+
from selenium.webdriver.common.by import By
4+
from selenium.webdriver.firefox.options import Options
5+
from selenium.webdriver.support import expected_conditions as EC
6+
from selenium.webdriver.support.ui import WebDriverWait
7+
8+
options = Options()
9+
options.add_argument("--headless")
10+
driver = webdriver.Firefox(options=options)
11+
driver.get("https://bandcamp.com/discover/")
12+
13+
tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
14+
print(len(tracks))
15+
16+
try:
17+
cookie_accept_button = driver.find_element(
18+
By.CSS_SELECTOR,
19+
"#cookie-control-dialog button.g-button.outline",
20+
)
21+
cookie_accept_button.click()
22+
except NoSuchElementException:
23+
pass
24+
25+
pagination_button = driver.find_element(By.ID, "view-more")
26+
pagination_button.click()
27+
28+
wait = WebDriverWait(driver, 10)
29+
wait.until(EC.element_to_be_clickable((By.ID, "view-more")))
30+
31+
tracks = driver.find_elements(By.CLASS_NAME, "results-grid-item")
32+
print(len(tracks))
33+
34+
driver.quit()

0 commit comments

Comments
 (0)