Repository: pterm/pterm (go)

Issue:
Add ability to set custom io.writter for each of the different output levels
This would allow for errors to be written to standard error or for debug logs to be written to a file.

Pull request:
feat: custom writer for progressbar and spinner printers
### Description
Adds a custom writer to the progress bar and spinner components.

### Scope
> What is affected by this pull request?
ProgressBar and Spinner

- [ ] Bug Fix
- [x] New Feature
- [ ] Documentation
- [ ] Other

### Related Issue
Fixes #261 


### To-Do Checklist
- [ ] I tested my changes
- [x] I have commented every method that I created/changed
- [ ] I updated the examples to fit with my changes
- [x] I have added tests for my newly created methods


Patch:
diff --git a/docs/docs/printer/progressbar.md b/docs/docs/printer/progressbar.md
index 89531950..c06789ec 100644
--- a/docs/docs/printer/progressbar.md
+++ b/docs/docs/printer/progressbar.md
@@ -1,6 +1,6 @@
 # ProgressbarPrinter
 
-<!-- 
+<!--
 Replace all of the following strings with the current printer.
      progressbar Progressbar ProgressbarPrinter DefaultProgressbar
 -->
@@ -9,7 +9,6 @@ Replace all of the following strings with the current printer.
 
 <p align="center"><a href="https://github.com/pterm/pterm/blob/master/_examples/progressbar/main.go" target="_blank">(Show source of demo)</a></p>
 
-
 ## Usage
 
 ### Basic usage
@@ -23,12 +22,12 @@ progressbar.Increment()
 
 ### Functions
 
-|Function|Description|
-|--------|-----------|
-|[Add(count int)](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.Add)|Add `count` to current value.|
-|[GetElapsedTime()](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.GetElapsedTime)|GetElapsedTime returns the elapsed time, since the ProgressbarPrinter was started.|
-|[Increment()](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.Increment)|Increment current value by one.|
-|[UpdateTitle(title string)](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.UpdateTitle)|Update the progressbar's title.|
+| Function                                                                                              | Description                                                                        |
+| ----------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
+| [Add(count int)](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.Add)                    | Add `count` to current value.                                                      |
+| [GetElapsedTime()](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.GetElapsedTime)       | GetElapsedTime returns the elapsed time, since the ProgressbarPrinter was started. |
+| [Increment()](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.Increment)                 | Increment current value by one.                                                    |
+| [UpdateTitle(title string)](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.UpdateTitle) | Update the progressbar's title.                                                    |
 
 ### Options
 
@@ -41,38 +40,40 @@ progressbar.Increment()
 > [!TIP]
 > Click the options and types to show the documentation on _pkg.go.dev_
 
-|Option|Type|
-|------|----|
-|[BarCharacter](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithBarCharacter)|string|
-|[BarStyle](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithBarStyle)|[*Style](https://pkg.go.dev/github.com/pterm/pterm#Style)|
-|[Current](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithCurrent)|int|
-|[ElapsedTimeRoundingFactor](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithElapsedTimeRoundingFactor)|time.Duration|
-|[LastCharacter](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithLastCharacter)|string|
-|[RemoveWhenDone](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithRemoveWhenDone)|...bool|
-|[ShowCount](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowCount)|...bool|
-|[ShowElapsedTime](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowElapsedTime)|...bool|
-|[ShowPercentage](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowPercentage)|...bool|
-|[ShowTitle](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowTitle)|...bool|
-|[Title](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithTitle)|string|
-|[TitleStyle](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithTitleStyle)|[*Style](https://pkg.go.dev/github.com/pterm/pterm#Style)|
-|[Total](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithTotal)|int|
-|[BarFiller](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithBarFiller)|string|
-|[MaxWidth](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithMaxWidth)|int|
+| Option                                                                                                                  | Type                                                       |
+| ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
+| [BarCharacter](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithBarCharacter)                           | string                                                     |
+| [BarStyle](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithBarStyle)                                   | [\*Style](https://pkg.go.dev/github.com/pterm/pterm#Style) |
+| [Current](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithCurrent)                                     | int                                                        |
+| [ElapsedTimeRoundingFactor](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithElapsedTimeRoundingFactor) | time.Duration                                              |
+| [LastCharacter](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithLastCharacter)                         | string                                                     |
+| [RemoveWhenDone](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithRemoveWhenDone)                       | ...bool                                                    |
+| [ShowCount](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowCount)                                 | ...bool                                                    |
+| [ShowElapsedTime](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowElapsedTime)                     | ...bool                                                    |
+| [ShowPercentage](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowPercentage)                       | ...bool                                                    |
+| [ShowTitle](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithShowTitle)                                 | ...bool                                                    |
+| [Title](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithTitle)                                         | string                                                     |
+| [TitleStyle](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithTitleStyle)                               | [\*Style](https://pkg.go.dev/github.com/pterm/pterm#Style) |
+| [Total](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithTotal)                                         | int                                                        |
+| [BarFiller](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithBarFiller)                                 | string                                                     |
+| [MaxWidth](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithMaxWidth)                                   | int                                                        |
+| [Writer](https://pkg.go.dev/github.com/pterm/pterm#ProgressbarPrinter.WithWriter)                                       | io.Writer                                                  |
 
 ### Output functions
 
 > This printer implements the interface [`LivePrinter`](https://github.com/pterm/pterm/blob/master/interface_live_printer.go)
 
-|Function|Description|
-|------|---------|
-|Start()|Returns itself and possible errors|
-|Stop()|Returns itself and possible errors|
-|GenericStart()|Returns the started LivePrinter and possible errors|
-|GenericStop()|Returns the stopped LivePrinter and possible errors|
+| Function       | Description                                         |
+| -------------- | --------------------------------------------------- |
+| Start()        | Returns itself and possible errors                  |
+| Stop()         | Returns itself and possible errors                  |
+| GenericStart() | Returns the started LivePrinter and possible errors |
+| GenericStop()  | Returns the stopped LivePrinter and possible errors |
 
 > [!NOTE]
 > The generic start and stop methods are only used to implement the printer into the interface.
 > Use the normal `Start()` and `Stop()` methods if possible.
 
 ## Related
+
 - [Override default printers](docs/customizing/override-default-printer.md)
diff --git a/docs/docs/printer/spinner.md b/docs/docs/printer/spinner.md
index 6e09c9cc..6c41b1c7 100644
--- a/docs/docs/printer/spinner.md
+++ b/docs/docs/printer/spinner.md
@@ -1,6 +1,6 @@
 # SpinnerPrinter
 
-<!-- 
+<!--
 Replace all of the following strings with the current printer.
      spinner Spinner SpinnerPrinter DefaultSpinner
 -->
@@ -9,7 +9,6 @@ Replace all of the following strings with the current printer.
 
 <p align="center"><a href="https://github.com/pterm/pterm/blob/master/_examples/spinner/main.go" target="_blank">(Show source of demo)</a></p>
 
-
 ## Usage
 
 ### Basic usage
@@ -20,12 +19,12 @@ pterm.DefaultSpinner.Start()
 
 ### Functions
 
-|Function|Description|
-|--------|-----------|
-|[Fail(message ...interface{})](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.Fail)|Fail displays the fail printer. If no message is given, the text of the SpinnerPrinter will be reused as the default message.|
-|[Success(message ...interface{})](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.Success)|Success displays the success printer. If no message is given, the text of the SpinnerPrinter will be reused as the default message.|
-|[UpdateText(text string)](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.UpdateText)|UpdateText updates the message of the active SpinnerPrinter. Can be used live.|
-|[Warning(message ...interface{})](https://pkg.go.dev/github.com/pterm/pterm#TemplatePrinter.UpdateText)|Warning displays the warning printer. If no message is given, the text of the SpinnerPrinter will be reused as the default message.|
+| Function                                                                                               | Description                                                                                                                         |
+| ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
+| [Fail(message ...interface{})](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.Fail)          | Fail displays the fail printer. If no message is given, the text of the SpinnerPrinter will be reused as the default message.       |
+| [Success(message ...interface{})](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.Success)    | Success displays the success printer. If no message is given, the text of the SpinnerPrinter will be reused as the default message. |
+| [UpdateText(text string)](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.UpdateText)         | UpdateText updates the message of the active SpinnerPrinter. Can be used live.                                                      |
+| [Warning(message ...interface{})](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.UpdateText) | Warning displays the warning printer. If no message is given, the text of the SpinnerPrinter will be reused as the default message. |
 
 ### Options
 
@@ -38,33 +37,34 @@ pterm.DefaultSpinner.Start()
 > [!TIP]
 > Click the options and types to show the documentation on _pkg.go.dev_
 
-|Option|Type|
-|------|----|
-|[Delay](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithDelay)|time.Duration|
-|[MessageStyle](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithMessageStyle)|[*Style](https://pkg.go.dev/github.com/pterm/pterm#Style)|
-|[RemoveWhenDone](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithRemoveWhenDone)|...bool|
-|[Sequence](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithSequence)|...string|
-|[Style](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithStyle)|[*Style](https://pkg.go.dev/github.com/pterm/pterm#Style)|
-|[Text](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithText)|string|
-|[ShowTimer](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithShowTimer)|...bool|
-|[TimerRoundingFactor](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithTimerRoundingFactor)|time.Duration|
-|[TimerStyle](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithTimerStyle)|[*Style](https://pkg.go.dev/github.com/pterm/pterm#Style)|
-
+| Option                                                                                                  | Type                                                       |
+| ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- |
+| [Delay](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithDelay)                             | time.Duration                                              |
+| [MessageStyle](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithMessageStyle)               | [\*Style](https://pkg.go.dev/github.com/pterm/pterm#Style) |
+| [RemoveWhenDone](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithRemoveWhenDone)           | ...bool                                                    |
+| [Sequence](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithSequence)                       | ...string                                                  |
+| [Style](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithStyle)                             | [\*Style](https://pkg.go.dev/github.com/pterm/pterm#Style) |
+| [Text](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithText)                               | string                                                     |
+| [ShowTimer](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithShowTimer)                     | ...bool                                                    |
+| [TimerRoundingFactor](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithTimerRoundingFactor) | time.Duration                                              |
+| [TimerStyle](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithTimerStyle)                   | [\*Style](https://pkg.go.dev/github.com/pterm/pterm#Style) |
+| [Writer](https://pkg.go.dev/github.com/pterm/pterm#SpinnerPrinter.WithWriter)                           | io.Writer                                                  |
 
 ### Output functions
 
 > This printer implements the interface [`LivePrinter`](https://github.com/pterm/pterm/blob/master/interface_live_printer.go)
 
-|Function|Description|
-|------|---------|
-|Start()|Returns itself and possible errors|
-|Stop()|Returns itself and possible errors|
-|GenericStart()|Returns the started LivePrinter and possible errors|
-|GenericStop()|Returns the stopped LivePrinter and possible errors|
+| Function       | Description                                         |
+| -------------- | --------------------------------------------------- |
+| Start()        | Returns itself and possible errors                  |
+| Stop()         | Returns itself and possible errors                  |
+| GenericStart() | Returns the started LivePrinter and possible errors |
+| GenericStop()  | Returns the stopped LivePrinter and possible errors |
 
 > [!NOTE]
 > The generic start and stop methods are only used to implement the printer into the interface.
 > Use the normal `Start()` and `Stop()` methods if possible.
 
 ## Related
-- [Override default printers](docs/customizing/override-default-printer.md)
\ No newline at end of file
+
+- [Override default printers](docs/customizing/override-default-printer.md)
diff --git a/print.go b/print.go
index d1e4624b..2402f495 100644
--- a/print.go
+++ b/print.go
@@ -106,7 +106,7 @@ func Fprint(writer io.Writer, a ...interface{}) {
 	var printed bool
 
 	for _, bar := range ActiveProgressBarPrinters {
-		if bar.IsActive {
+		if bar.IsActive && bar.Writer == writer {
 			ret += sClearLine()
 			ret += Sprinto(a...)
 			printed = true
@@ -114,7 +114,7 @@ func Fprint(writer io.Writer, a ...interface{}) {
 	}
 
 	for _, spinner := range activeSpinnerPrinters {
-		if spinner.IsActive {
+		if spinner.IsActive && spinner.Writer == writer {
 			ret += sClearLine()
 			ret += Sprinto(a...)
 			printed = true
@@ -124,7 +124,7 @@ func Fprint(writer io.Writer, a ...interface{}) {
 	if !printed {
 		ret = color.Sprint(Sprint(a...))
 	}
-
+	
 	if writer != nil {
 		color.Fprint(writer, Sprint(ret))
 	} else {
diff --git a/progressbar_printer.go b/progressbar_printer.go
index c1c9d037..90d84aac 100644
--- a/progressbar_printer.go
+++ b/progressbar_printer.go
@@ -1,6 +1,7 @@
 package pterm
 
 import (
+	"io"
 	"strconv"
 	"strings"
 	"time"
@@ -55,6 +56,8 @@ type ProgressbarPrinter struct {
 	IsActive bool
 
 	startedAt time.Time
+
+	Writer io.Writer
 }
 
 // WithTitle sets the name of the ProgressbarPrinter.
@@ -149,6 +152,12 @@ func (p ProgressbarPrinter) WithBarFiller(char string) *ProgressbarPrinter {
 	return &p
 }
 
+// WithWriter sets the custom Writer.
+func (p ProgressbarPrinter) WithWriter(writer io.Writer) *ProgressbarPrinter {
+	p.Writer = writer
+	return &p
+}
+
 // Increment current value by one.
 func (p *ProgressbarPrinter) Increment() *ProgressbarPrinter {
 	p.Add(1)
@@ -227,7 +236,7 @@ func (p *ProgressbarPrinter) updateProgress() *ProgressbarPrinter {
 	}
 
 	if !RawOutput {
-		Printo(before + bar + after)
+		Fprinto(p.Writer, before+bar+after)
 	}
 	return p
 }
@@ -250,7 +259,7 @@ func (p *ProgressbarPrinter) Add(count int) *ProgressbarPrinter {
 // Start the ProgressbarPrinter.
 func (p ProgressbarPrinter) Start() (*ProgressbarPrinter, error) {
 	if RawOutput && p.ShowTitle {
-		Println(p.Title)
+		Fprintln(p.Writer, p.Title)
 	}
 	p.IsActive = true
 	ActiveProgressBarPrinters = append(ActiveProgressBarPrinters, &p)
@@ -268,10 +277,10 @@ func (p *ProgressbarPrinter) Stop() (*ProgressbarPrinter, error) {
 	}
 	p.IsActive = false
 	if p.RemoveWhenDone {
-		fClearLine(nil)
-		Fprinto(nil)
+		fClearLine(p.Writer)
+		Fprinto(p.Writer)
 	} else {
-		Fprintln(nil)
+		Fprintln(p.Writer)
 	}
 	return p, nil
 }
diff --git a/spinner_printer.go b/spinner_printer.go
index 344e028f..8516d641 100644
--- a/spinner_printer.go
+++ b/spinner_printer.go
@@ -1,6 +1,7 @@
 package pterm
 
 import (
+	"io"
 	"time"
 
 	"github.com/pterm/pterm/internal"
@@ -43,6 +44,8 @@ type SpinnerPrinter struct {
 
 	startedAt       time.Time
 	currentSequence string
+
+	Writer io.Writer
 }
 
 // WithText adds a text to the SpinnerPrinter.
@@ -99,16 +102,22 @@ func (s SpinnerPrinter) WithTimerStyle(style *Style) *SpinnerPrinter {
 	return &s
 }
 
+// WithWriter sets the custom Writer.
+func (p SpinnerPrinter) WithWriter(writer io.Writer) *SpinnerPrinter {
+	p.Writer = writer
+	return &p
+}
+
 // UpdateText updates the message of the active SpinnerPrinter.
 // Can be used live.
 func (s *SpinnerPrinter) UpdateText(text string) {
 	s.Text = text
 	if !RawOutput {
-		fClearLine(nil)
-		Fprinto(nil, s.Style.Sprint(s.currentSequence)+" "+s.MessageStyle.Sprint(s.Text))
+		fClearLine(s.Writer)
+		Fprinto(s.Writer, s.Style.Sprint(s.currentSequence)+" "+s.MessageStyle.Sprint(s.Text))
 	}
 	if RawOutput {
-		Fprintln(nil, s.Text)
+		Fprintln(s.Writer, s.Text)
 	}
 }
 
@@ -123,7 +132,7 @@ func (s SpinnerPrinter) Start(text ...interface{}) (*SpinnerPrinter, error) {
 	}
 
 	if RawOutput {
-		Fprintln(nil, s.Text)
+		Fprintln(s.Writer, s.Text)
 	}
 
 	go func() {
@@ -137,7 +146,7 @@ func (s SpinnerPrinter) Start(text ...interface{}) (*SpinnerPrinter, error) {
 				if s.ShowTimer {
 					timer = " (" + time.Since(s.startedAt).Round(s.TimerRoundingFactor).String() + ")"
 				}
-				Fprinto(nil, s.Style.Sprint(seq)+" "+s.MessageStyle.Sprint(s.Text)+s.TimerStyle.Sprint(timer))
+				Fprinto(s.Writer, s.Style.Sprint(seq)+" "+s.MessageStyle.Sprint(s.Text)+s.TimerStyle.Sprint(timer))
 				s.currentSequence = seq
 				time.Sleep(s.Delay)
 			}
@@ -151,10 +160,10 @@ func (s SpinnerPrinter) Start(text ...interface{}) (*SpinnerPrinter, error) {
 func (s *SpinnerPrinter) Stop() error {
 	s.IsActive = false
 	if s.RemoveWhenDone {
-		fClearLine(nil)
-		Fprinto(nil)
+		fClearLine(s.Writer)
+		Fprinto(s.Writer)
 	} else {
-		Fprintln(nil)
+		Fprintln(s.Writer)
 	}
 	return nil
 }
@@ -187,8 +196,8 @@ func (s *SpinnerPrinter) Success(message ...interface{}) {
 	if len(message) == 0 {
 		message = []interface{}{s.Text}
 	}
-	fClearLine(nil)
-	Fprinto(nil, s.SuccessPrinter.Sprint(message...))
+	fClearLine(s.Writer)
+	Fprinto(s.Writer, s.SuccessPrinter.Sprint(message...))
 	_ = s.Stop()
 }
 
@@ -202,8 +211,8 @@ func (s *SpinnerPrinter) Fail(message ...interface{}) {
 	if len(message) == 0 {
 		message = []interface{}{s.Text}
 	}
-	fClearLine(nil)
-	Fprinto(nil, s.FailPrinter.Sprint(message...))
+	fClearLine(s.Writer)
+	Fprinto(s.Writer, s.FailPrinter.Sprint(message...))
 	_ = s.Stop()
 }
 
@@ -217,7 +226,7 @@ func (s *SpinnerPrinter) Warning(message ...interface{}) {
 	if len(message) == 0 {
 		message = []interface{}{s.Text}
 	}
-	fClearLine(nil)
-	Fprinto(nil, s.WarningPrinter.Sprint(message...))
+	fClearLine(s.Writer)
+	Fprinto(s.Writer, s.WarningPrinter.Sprint(message...))
 	_ = s.Stop()
 }


Test patch:
diff --git a/progressbar_printer_test.go b/progressbar_printer_test.go
index 961b77cc..70417d6c 100644
--- a/progressbar_printer_test.go
+++ b/progressbar_printer_test.go
@@ -1,6 +1,8 @@
 package pterm_test
 
 import (
+	"io"
+	"os"
 	"testing"
 	"time"
 
@@ -209,3 +211,43 @@ func TestProgressbarPrinter_UpdateTitle(t *testing.T) {
 
 	testza.AssertEqual(t, "test2", p2.Title)
 }
+
+func TestProgressbarPrinter_WithWriter(t *testing.T) {
+	p := pterm.ProgressbarPrinter{}
+	s := os.Stderr
+	p2 := p.WithWriter(s)
+
+	testza.AssertEqual(t, s, p2.Writer)
+	testza.AssertZero(t, p.Writer)
+}
+
+func TestProgressbarPrinter_OutputToWriters(t *testing.T) {
+	testCases := map[string]struct {
+		action                func(*pterm.ProgressbarPrinter)
+		expectOutputToContain string
+	}{
+		"ExpectUpdatedTitleToBeWrittenToStderr": {
+			action: func(pb *pterm.ProgressbarPrinter) {
+				pb.UpdateTitle("Updated text")
+			},
+			expectOutputToContain: "Updated text",
+		},
+	}
+
+	for testTitle, testCase := range testCases {
+		t.Run(testTitle, func(t *testing.T) {
+			stderr, err := testza.CaptureStderr(func(w io.Writer) error {
+				pb, err := pterm.DefaultProgressbar.WithTitle("Hello world").WithWriter(os.Stderr).Start()
+				time.Sleep(time.Second) // Required otherwise the goroutine doesn't run and the text isnt outputted
+				testza.AssertNoError(t, err)
+				testCase.action(pb)
+				time.Sleep(time.Second) // Required otherwise the goroutine doesn't run and the text isnt updated
+				return nil
+			})
+
+			testza.AssertNoError(t, err)
+			testza.AssertContains(t, stderr, "Hello world")
+			testza.AssertContains(t, stderr, testCase.expectOutputToContain)
+		})
+	}
+}
diff --git a/spinner_printer_test.go b/spinner_printer_test.go
index 4fa7f8cb..1f78d5ac 100644
--- a/spinner_printer_test.go
+++ b/spinner_printer_test.go
@@ -2,6 +2,7 @@ package pterm_test
 
 import (
 	"io"
+	"os"
 	"testing"
 	"time"
 
@@ -209,6 +210,54 @@ func TestSpinnerPrinter_DifferentVariations(t *testing.T) {
 	}
 }
 
+func TestSpinnerPrinter_WithWriter(t *testing.T) {
+	p := pterm.SpinnerPrinter{}
+	s := os.Stderr
+	p2 := p.WithWriter(s)
+
+	testza.AssertEqual(t, s, p2.Writer)
+	testza.AssertZero(t, p.Writer)
+}
+
+func TestSpinnerPrinter_OutputToWriters(t *testing.T) {
+	testCases := map[string]struct {
+		action                func(*pterm.SpinnerPrinter)
+		expectOutputToContain string
+	}{
+		"ExpectWarningMessageToBeWrittenToStderr": {
+			action:                func(sp *pterm.SpinnerPrinter) { sp.Warning("A warning") },
+			expectOutputToContain: "A warning",
+		},
+		"ExpectFailMessageToBeWrittenToStderr": {
+			action:                func(sp *pterm.SpinnerPrinter) { sp.Fail("An error") },
+			expectOutputToContain: "An error",
+		},
+		"ExpectUpdatedTextToBeWrittenToStderr": {
+			action: func(sp *pterm.SpinnerPrinter) {
+				sp.UpdateText("Updated text")
+			},
+			expectOutputToContain: "Updated text",
+		},
+	}
+
+	for testTitle, testCase := range testCases {
+		t.Run(testTitle, func(t *testing.T) {
+			stderr, err := testza.CaptureStderr(func(w io.Writer) error {
+				sp, err := pterm.DefaultSpinner.WithText("Hello world").WithWriter(os.Stderr).Start()
+				time.Sleep(time.Second) // Required otherwise the goroutine doesn't run and the text isnt outputted
+				testza.AssertNoError(t, err)
+				testCase.action(sp)
+				time.Sleep(time.Second) // Required otherwise the goroutine doesn't run and the text isnt updated
+				return nil
+			})
+
+			testza.AssertNoError(t, err)
+			testza.AssertContains(t, stderr, "Hello world")
+			testza.AssertContains(t, stderr, testCase.expectOutputToContain)
+		})
+	}
+}
+
 // func TestClearActiveSpinners(t *testing.T) {
 // 	activeSpinnerPrinters = []*pterm.SpinnerPrinter{}
 // }