-
Notifications
You must be signed in to change notification settings - Fork 5.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add slimmed-down Selenium project code #596
base: master
Are you sure you want to change the base?
Conversation
@bzaczynski this is my attempt to create a slimmed-down version of the project that primarily focuses on Selenium web interactions (now following POM), but also keep the basic functionality of the music player (and a reduced---but existing---separation of concerns). I've tried to build the project in a way so that it can lead into the project that you wrote, with the idea in mind that learners could follow your SbSP as a next step if they're interested in the music player project. At the same time, I tried to keep it simple and reduced enough that learners who are only there for an intro to Selenium would get their money's worth. Hope this works and looking forward to your thoughts! Linking the other PR for reference: #583 |
python-selenium/README.md
Outdated
|
||
## Run the Bandcamp Discover Player | ||
|
||
To run the music placer, navigate to the `src/` folder, then execute the module from your command-line: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✍️ Typo
To run the music placer, navigate to the `src/` folder, then execute the module from your command-line: | |
To run the music player, navigate to the `src/` folder, then execute the module from your command-line: |
python-selenium/README.md
Outdated
(venv) $ cd src/ | ||
(venv) $ python -m bandcamp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
That doesn't work:
(venv) $ cd src/
(venv) $ python -m bandcamp/
/home/bartek/.virtualenvs/python-selenium/bin/python: No module named bandcamp/
A better option would be to install the package into the venv first. That way, it won't matter where you'll run the module from:
(venv) $ python -m pip install .
(venv) $ python -m bandcamp/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@martin-martin I left a few comments for you. Let me know what you think.
(venv) $ python -m bandcamp | ||
``` | ||
|
||
You'll see a text-based user interface that allows you to interact with the music player: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤝 Handholding
It seems that you implicitly assume there's a driver for Firefox installed and configured for this to run. I don't have one, and this is what I got:
(venv) $ python -m bandcamp
Traceback (most recent call last):
...
selenium.common.exceptions.InvalidArgumentException: Message: binary is not a Firefox executable
Probably better to give a heads-up or provide instructions on what needs to be installed before.
python-selenium/README.md
Outdated
You'll see a text-based user interface that allows you to interact with the music player: | ||
|
||
``` | ||
Type: [play <track number>], [tracks], [more], [exit] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Flagging the message (I'll explain why in another comment.)
python-selenium/pyproject.toml
Outdated
[tool.setuptools.packages.find] | ||
where = ["src"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
These two lines are redundant, as setuptools will automatically discover Python packages following the src-layout.
python-selenium/requirements.txt
Outdated
appdirs==1.4.4 | ||
attrs==24.2.0 | ||
certifi==2024.7.4 | ||
h11==0.14.0 | ||
idna==3.7 | ||
jedi==0.19.1 | ||
outcome==1.3.0.post0 | ||
parso==0.8.4 | ||
prompt_toolkit==3.0.47 | ||
ptpython==3.0.29 | ||
Pygments==2.18.0 | ||
PySocks==1.7.1 | ||
selenium==4.23.1 | ||
sniffio==1.3.1 | ||
sortedcontainers==2.4.0 | ||
trio==0.26.1 | ||
trio-websocket==0.11.1 | ||
typing_extensions==4.12.2 | ||
urllib3==2.2.2 | ||
wcwidth==0.2.13 | ||
websocket-client==1.8.0 | ||
wsproto==1.2.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
Are you sure you haven't accidentally installed any extra packages besides Selenium? Here's what my requirements.txt file looks like:
attrs==24.2.0
certifi==2024.8.30
h11==0.14.0
idna==3.10
outcome==1.3.0.post0
PySocks==1.7.1
selenium==4.25.0
sniffio==1.3.1
sortedcontainers==2.4.0
trio==0.27.0
trio-websocket==0.11.1
typing_extensions==4.12.2
urllib3==2.2.3
websocket-client==1.8.0
wsproto==1.2.0
if __name__ == "__main__": | ||
main() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
Do we need those two lines here? Since you defined the bandcamp-player
script in pyproject.toml
, you can invoke the main()
function directly:
$ bandcamp-player
Unless, you do want to have two ways of running your TUI:
bandcamp-player
python -m bandcamp
from bandcamp.app.player import Player | ||
|
||
|
||
class TUI: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
Since this class has no state, only behaviors, there's no need to define it. I'd strongly suggest replacing your class methods with simple top-level functions. It'll make the code less Java-esque ☕
|
||
|
||
def main(): | ||
"""Provides the main entry point for the app.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
According to the widely-adopted docstring conventions (PEP 257), this sort of comments should use the imperative form, e.g.:
"""Provides the main entry point for the app.""" | |
"""Provide the main entry point for the app.""" |
player.play(track_number) | ||
print(player._current_track._get_track_info()) | ||
|
||
def tracks(self, player): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
Can we use a more descriptive name for this method, such as this?
def tracks(self, player): | |
def display_tracks(self, player): |
It would render the dostring that follows less important 😉
class TUI: | ||
"""Provides a text-based user interface for a Bandcamp music player.""" | ||
|
||
COLUMN_WIDTH = CW = 30 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice aliasing!
class HomePageLocatorMixin: | ||
DISCOVER_RESULTS = (By.CLASS_NAME, "discover-results") | ||
TRACK = (By.CLASS_NAME, "discover-item") | ||
PAGINATION_BUTTON = (By.CLASS_NAME, "item-page") | ||
|
||
|
||
class TrackLocatorMixin: | ||
PLAY_BUTTON = (By.CSS_SELECTOR, "a") | ||
ALBUM = (By.CLASS_NAME, "item-title") | ||
GENRE = (By.CLASS_NAME, "item-genre") | ||
ARTIST = (By.CLASS_NAME, "item-artist") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔧 Technical Recommendation
I really like your approach of using the "mixin" classes to store various element locators. It's a clever way to keep them separate but close at the same time 👏
That being said, I'm not convinced we should call these classes mixins. A mixin class encapsulates behavior, and it typically depends on some attributes defined in the target class, so a mixin can't exist on its own—it needs to be "mixed-in" with another ingredient.
In this case, your classes are just data containers or convenient namespaces for a bunch of constants that can stand on their own. So, I'd suggest to drop the "Mixin" suffix from their names.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@martin-martin Perfecto ✨ Thanks for a quick update! Can't wait to review the rest of it 😉
Where to put new files:
my-awesome-article
How to merge your changes: