Fill Strategies

This tutorial explains Widgetastic’s built-in fill strategies for handling form filling operations. You’ll learn about DefaultFillViewStrategy and WaitFillViewStrategy, how they work, and when to use each one.

Learning Objectives

By completing this tutorial, you will:

  • ✅ Understand how fill strategies work in Widgetastic

  • ✅ Learn when to use DefaultFillViewStrategy vs WaitFillViewStrategy

  • ✅ Use respect_parent for strategy inheritance in nested views

Understanding Fill Strategies

Fill strategies control how widgets are filled in a View. When you call view.fill(values), the view uses its configured fill strategy to determine:

  • The order widgets should be filled

  • Whether to wait for widgets to appear before filling

  • How to handle errors during filling

  • What to log during fill operations

Widgetastic provides two built-in fill strategies:

Built-in Fill Strategies

  • DefaultFillViewStrategy: Fills widgets sequentially without waiting

  • WaitFillViewStrategy: Waits for each widget to be displayed before filling

DefaultFillViewStrategy

DefaultFillViewStrategy is the default strategy used by all Views. It fills widgets in the order they appear in the view’s widget_names attribute.

Key Features:

  • Fills widgets sequentially in order

  • No waiting for widgets to appear

  • Skips widgets with None values

  • Warns about extra keys that don’t match widgets

  • Handles widgets without fill methods gracefully

  • Returns True if any widget value changed

Basic Usage:

"""DefaultFillViewStrategy Examples

This comprehensive example demonstrates all aspects of DefaultFillViewStrategy.
"""

from widgetastic.utils import DefaultFillViewStrategy
from widgetastic.widget import View, TextInput, Checkbox, Widget


class BasicForm(View):
    input1 = TextInput(name="input1")
    input2 = TextInput(name="fill_with_2")
    checkbox1 = Checkbox(id="input2")

    # Explicitly set the default strategy (optional - it's the default)
    fill_strategy = DefaultFillViewStrategy()


# Create view instance
view = BasicForm(browser)  # noqa: F821

# Fill multiple widgets at once
changed = view.fill({"input1": "test_value", "checkbox1": True})

print(f"Fill changed values: {changed}")
print(f"Current values: {view.read()}")

Filtering None Values:

Values set to None are automatically filtered out:

values_with_none = {
    "input1": "value1",
    "input2": None,  # This will be filtered out
    "checkbox1": True,
}

view.fill(values_with_none)
print(f"After filling with None values: {view.read()}")

Handling Extra Keys:

The strategy warns about keys in your fill data that don’t correspond to widgets:

import logging  # noqa: E402

logging.basicConfig(level=logging.WARNING)

values_with_extras = {
    "input1": "value1",
    "nonexistent_widget": "value2",  # This doesn't exist
    "another_extra": "value3",  # This doesn't exist either
}

# When filling, you'll get a warning in logs:
# "Extra values that have no corresponding fill fields passed: another_extra, nonexistent_widget"
view.fill(values_with_extras)

Handling Widgets Without Fill Methods:

Widgets that don’t have a fill() method are skipped with a warning:

class NoFillWidget(Widget):
    """Widget without fill method."""

    pass


class TestForm(View):
    input1 = TextInput(name="input1")
    no_fill_widget = NoFillWidget()
    input2 = TextInput(name="fill_with_2")

    fill_strategy = DefaultFillViewStrategy()


test_view = TestForm(browser)  # noqa: F821

# Fill operation will skip no_fill_widget and log a warning
values = {
    "input1": "value1",
    "no_fill_widget": "will_skip",  # This will be skipped
    "input2": "value2",
}

result = test_view.fill(values)
print(f"Fill result: {result}")
print(f"Current values: {test_view.read()}")

Change Detection:

The strategy returns True only if at least one widget value actually changed:

# First fill - values are new, so returns True
result1 = view.fill({"input1": "test_value", "checkbox1": True})
print(f"First fill result: {result1}")

# Second fill with same values - no change, returns False
result2 = view.fill({"input1": "test_value", "checkbox1": True})
print(f"Second fill result: {result2}")

WaitFillViewStrategy

WaitFillViewStrategy extends DefaultFillViewStrategy by adding wait functionality. Before filling each widget, it waits for the widget to be displayed.

Key Features: * Inherits all features from DefaultFillViewStrategy * Waits for each widget to be displayed before filling * Configurable wait timeout per widget * Ideal for dynamic content that appears after user interactions * Handles Single Page Applications (SPAs) with async loading

When to Use:

Use WaitFillViewStrategy when: * Widgets may appear dynamically after page load * Forms have conditional fields that appear based on selections * Widgets are inside iframes that load slowly * Forms have progressive revelation of fields

Basic Usage:

"""WaitFillViewStrategy Examples

This comprehensive example demonstrates WaitFillViewStrategy usage.
"""

from widgetastic.utils import WaitFillViewStrategy
from widgetastic.widget import View, TextInput, Checkbox


class DynamicForm(View):
    input1 = TextInput(name="input1")
    checkbox1 = Checkbox(id="input2")

    # Use wait strategy with default 5-second timeout
    fill_strategy = WaitFillViewStrategy()


view = DynamicForm(browser)  # noqa: F821

# Fill operation will wait for each widget to be displayed
changed = view.fill({"input1": "wait_test_value", "checkbox1": True})

print(f"Fill changed values: {changed}")
print(f"Current values: {view.read()}")

Custom Wait Timeout:

Configure how long to wait for each widget:

class DynamicFormCustomTimeout(View):
    input1 = TextInput(name="input1")
    input2 = TextInput(name="fill_with_2")
    checkbox1 = Checkbox(id="input2")

    # Custom 10-second timeout per widget
    fill_strategy = WaitFillViewStrategy(wait_widget=10)


view_custom = DynamicFormCustomTimeout(browser)  # noqa: F821

# Each widget will wait up to 10 seconds to be displayed
view_custom.fill({"input1": "custom_wait_test", "input2": "another_value"})
print(f"Current values: {view_custom.read()}")

Strategy Inheritance with respect_parent

Both fill strategies support the respect_parent parameter, which controls whether child views inherit the parent’s fill strategy.

Understanding respect_parent:

  • respect_parent=False (default): Child views get their own default strategy

  • respect_parent=True: Child views inherit parent’s strategy

Example Without Inheritance:

from widgetastic.utils import WaitFillViewStrategy
from widgetastic.widget import View, TextInput


# Example: Without respect_parent (default behavior)
class ParentViewNoInherit(View):
    fill_strategy = WaitFillViewStrategy(wait_widget=10)

    @View.nested
    class ChildView(View):
        input1 = TextInput(name="input1")


parent_view = ParentViewNoInherit(browser)  # noqa: F821
print(f"Parent strategy: {type(parent_view.fill_strategy).__name__}")
print(f"Child strategy: {type(parent_view.ChildView.fill_strategy).__name__}")

Example With Inheritance:

# Example: With respect_parent=True
class ParentViewWithInherit(View):
    fill_strategy = WaitFillViewStrategy(respect_parent=True, wait_widget=10)

    @View.nested
    class ChildView(View):
        input1 = TextInput(name="input1")


parent_view2 = ParentViewWithInherit(browser)  # noqa: F821
print(f"Parent strategy: {type(parent_view2.fill_strategy).__name__}")
print(f"Child strategy: {type(parent_view2.ChildView.fill_strategy).__name__}")

Key takeaways:

  • Views automatically use DefaultFillViewStrategy if none specified

  • Use WaitFillViewStrategy for dynamic content that may not be immediately available

  • Fill strategies handle error cases gracefully (skipping widgets without fill methods)

  • Strategies respect widget order and filter None values automatically

This completes the fill strategies tutorial. You now understand how to use Widgetastic’s built-in fill strategies effectively in your automation tests.