Skip to content
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

Portal API that allows bypassing the automatic section scoping in a controlled way #12

Closed
wants to merge 8 commits into from

Conversation

khamusa
Copy link
Contributor

@khamusa khamusa commented Dec 20, 2024

This is a first attempt at solving the issue with verifying content on toast messages or interacting with floating date pickers that are placed in the root of the body element.

Check the changes to the README.md file for the proposed API.

@khamusa khamusa marked this pull request as draft December 20, 2024 22:34
@khamusa
Copy link
Contributor Author

khamusa commented Jan 6, 2025

When implementing some IAM tests with tabasco, I stumbled upon the need to interact with a floating datepicker element that is outside the form container. We already have use cases for interacting with our toast messages, and I'd rather not use the same portal for every possible component. Instead, we want our assertions to be specific, so we don't risk clicking on a toast message that happens to be on the page at the same time as a datepicker.

So having a single portal configuration makes little sense. I'm willing to extend the API with named portals instead:

Tabasco.configure do |config|
  config.portal(:toast_message) { find("[data-portal-container]") }
  config.portal(:date_picker) { find("[data-datepicker-thingy]") }
end

Then:

class MyForm < Tabasco::Section
  portal :date_picker                           # matches the datepicker portal by name
  portal :alert, type: :toast_message # matches the toast_message portal, but exposes it under a different name
end

Which could then be used as any other section/page objects:

my_form.date_picker.has_content!("loremipsum")
my_form.alert.has_content!("loremipsum dolor sit")

Thoughts?

@khamusa khamusa changed the title Introduce Portal API Portal API that allows bypassing the automatic section scoping in a controlled way Jan 6, 2025
@Oyster-Moura
Copy link

Oyster-Moura commented Jan 7, 2025

@khamusa

my_form.date_picker.has_content!("loremipsum")
my_form.alert.has_content!("loremipsum dolor sit")

This looks good but what happens if there's multiple matches for each type of portal? Because right now we would get an exception from the find.

Should we allow users to select them?

Tabasco.configure do |config|
  config.portal(:toast_messages) { all("[data-portal-container]") }
end

class MyForm < Tabasco::Section
  portal :alert, type: :toast_messages, select: -> { |all|  all.first } # , select: -> { |all|  all.find("#idk") }
end

Or when setting up the portal we just need to be careful only select one?

@silvabox
Copy link
Contributor

silvabox commented Jan 7, 2025

@khamusa love the idea 🎉.

@Oyster-Moura the idea of selecting a portal seems overly complicated to me. I think a portal should only ever resolve to a single element

@khamusa I find the approach to aliasing slightly less intuitive as it introduces the concept of a type. I'm always wary of using 'type' where an alternative might be found, because type is so often used in other contexts and can create ambiguity. If aliasing is going to be a thing (and if you have it once, it's almost inevitable!) then we should perhaps consider a convention that's going to be part of the Tabasco DSL. How about?:

class MyForm < Tabasco::Section
  portal :date_picker                           # matches the datepicker portal by name
  portal :alert, as: :toast_message    # matches the toast_message portal, but exposes it under a different name
end

# frozen_string_literal: true

module Tabasco
PreconditionNotMetError = Class.new(StandardError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would recommend all errors extend a TabascoError base class that wraps StandardError. That way implementers can write catchall handlers; i.e.:

rescue TabascoError => e
  # etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this was me quickly prototyping something 😓 . We even already have that base class defined as Tabasco::Error.

It's a good reminder, though, I'd likely forget it 😆

@khamusa
Copy link
Contributor Author

khamusa commented Jan 7, 2025

@Oyster-Moura for sections, we force users to specify a test_id, and force the usage of find behind the scenes, which will raise an error if multiple occurrences are found. I think this is a desired quality everywhere.

Or when setting up the portal we just need to be careful only select one?

This is precisely what tabasco aims to avoid! If we need to be careful to not mess something up, the framework is failing us! ;)

You raised a relevant point, but rather than enlarging the framework to account for more cases, I'd rather make it more restrictive. The root cause of the issue is the fact a config proc allows the user to locate the portal using arbitrary capybara finders. The fact all can find multiple occurrences is problematic, but, even worse, it does not wait for elements to appear on the page. So this decision we're placing at the core of the framework opens room for flakiness - going in the opposite direction of what we want (a framework that makes you safer without you even realizing it).

Limiting to only test_id for portals does not seem a good choice, though. Imagine our users will employ different UI libraries (with floating elements that often require portal logic), which may or may not be customizable for them to add a test_id in the precise DOM element they need. Datepicker components are often out-of-the-box components that don't offer a lot of customization options.

So I think we should drop the proc approach so we can enforce the use of find, but at the same time give users more find-compatible options to locate their portals:

# no extra arguments, defaults to find("[data-testid='toast_messages']) - consistent with the rest of the framework
config.portal(:toast_messages)

# can be overridden
config.portal(:toast_messages, test_id: :lorem_ipsum)

# other find-compatible finder approaches:
config.portal(:toast_messages, css: "div.portal.nice")
config.portal(:toast_messages, xpath: "...")

I'd go ahead and implement this new approach using test_ids only, knowing the API can be extended later with other finder methods.

WTDYT?

Multiple portals can be defined using Tabasco.configure, and a test_id is
required to locate their containers.
@khamusa
Copy link
Contributor Author

khamusa commented Jan 8, 2025

@silvabox / @Oyster-Moura I've added a second commit with what I believe would be the minimal implementation for this new API.

It currently allows you to define multiple portals and specify a different test_id. It does not allow finding containers using any other strategy, and aliases are not supported either at this stage. Although both features would be simple to add, I'd rather expand the Tabasco interfaces conservatively - the less, the better.

Tomorrow I'll experiment with using this new interface on an actual Oyster test... For now, let me know what you think!

btw: feel free to review the code, but don't expect much - it's quite late now, and I still need to review it myself before making it ready for review

@khamusa khamusa force-pushed the main branch 2 times, most recently from a7215bf to 5dd981c Compare January 9, 2025 21:11
@khamusa
Copy link
Contributor Author

khamusa commented Jan 10, 2025

This PR is being superseded by #22

@khamusa khamusa closed this Jan 10, 2025
@khamusa khamusa mentioned this pull request Jan 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants